#ifndef NIX_API_VALUE_H
#define NIX_API_VALUE_H

/** @addtogroup libexpr
 * @{
 */
/** @file
 * @brief libexpr C bindings dealing with values
 */

#include "nix_api_util.h"
#include "nix_api_store.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif
// cffi start

// Type definitions
typedef enum {
    NIX_TYPE_THUNK,
    NIX_TYPE_INT,
    NIX_TYPE_FLOAT,
    NIX_TYPE_BOOL,
    NIX_TYPE_STRING,
    NIX_TYPE_PATH,
    NIX_TYPE_NULL,
    NIX_TYPE_ATTRS,
    NIX_TYPE_LIST,
    NIX_TYPE_FUNCTION,
    NIX_TYPE_EXTERNAL
} ValueType;

// forward declarations
typedef struct nix_value nix_value;
typedef struct EvalState EvalState;

[[deprecated("use nix_value instead")]] typedef nix_value Value;

// type defs
/** @brief Stores an under-construction set of bindings
 * @ingroup value_manip
 *
 * Do not reuse.
 * @see nix_make_bindings_builder, nix_bindings_builder_free, nix_make_attrs
 * @see nix_bindings_builder_insert
 */
typedef struct BindingsBuilder BindingsBuilder;

/** @brief Stores an under-construction list
 * @ingroup value_manip
 *
 * Do not reuse.
 * @see nix_make_list_builder, nix_list_builder_free, nix_make_list
 * @see nix_list_builder_insert
 */
typedef struct ListBuilder ListBuilder;

/** @brief PrimOp function
 * @ingroup primops
 *
 * Owned by the GC
 * @see nix_alloc_primop, nix_init_primop
 */
typedef struct PrimOp PrimOp;
/** @brief External Value
 * @ingroup Externals
 *
 * Owned by the GC
 */
typedef struct ExternalValue ExternalValue;

/** @brief String without placeholders, and realised store paths
 */
typedef struct nix_realised_string nix_realised_string;

/** @defgroup primops Adding primops
 * @{
 */
/** @brief Function pointer for primops
 *
 * When you want to return an error, call nix_set_err_msg(context, NIX_ERR_UNKNOWN, "your error message here").
 *
 * @param[in] user_data Arbitrary data that was initially supplied to nix_alloc_primop
 * @param[out] context Stores error information.
 * @param[in] state Evaluator state
 * @param[in] args list of arguments. Note that these can be thunks and should be forced using nix_value_force before
 * use.
 * @param[out] ret return value
 * @see nix_alloc_primop, nix_init_primop
 */
typedef void (*PrimOpFun)(
    void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret);

/** @brief Allocate a PrimOp
 *
 * Owned by the garbage collector.
 * Use nix_gc_decref() when you're done with the returned PrimOp.
 *
 * @param[out] context Optional, stores error information
 * @param[in] fun callback
 * @param[in] arity expected number of function arguments
 * @param[in] name function name
 * @param[in] args array of argument names, NULL-terminated
 * @param[in] doc optional, documentation for this primop
 * @param[in] user_data optional, arbitrary data, passed to the callback when it's called
 * @return primop, or null in case of errors
 * @see nix_init_primop
 */
PrimOp * nix_alloc_primop(
    nix_c_context * context,
    PrimOpFun fun,
    int arity,
    const char * name,
    const char ** args,
    const char * doc,
    void * user_data);

/** @brief add a primop to the `builtins` attribute set
 *
 * Only applies to States created after this call.
 *
 * Moves your PrimOp content into the global evaluator
 * registry, meaning your input PrimOp pointer is no longer usable.
 * You are free to remove your references to it,
 * after which it will be garbage collected.
 *
 * @param[out] context Optional, stores error information
 * @return primop, or null in case of errors
 *
 */
nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp);
/** @} */

// Function prototypes

/** @brief Allocate a Nix value
 *
 * Owned by the GC. Use nix_gc_decref() when you're done with the pointer
 * @param[out] context Optional, stores error information
 * @param[in] state nix evaluator state
 * @return value, or null in case of errors
 *
 */
nix_value * nix_alloc_value(nix_c_context * context, EvalState * state);

/**
 * @brief Increment the garbage collector reference counter for the given `nix_value`.
 *
 * The Nix language evaluator C API keeps track of alive objects by reference counting.
 * When you're done with a refcounted pointer, call nix_value_decref().
 *
 * @param[out] context Optional, stores error information
 * @param[in] value The object to keep alive
 */
nix_err nix_value_incref(nix_c_context * context, nix_value * value);

/**
 * @brief Decrement the garbage collector reference counter for the given object
 *
 * @param[out] context Optional, stores error information
 * @param[in] value The object to stop referencing
 */
nix_err nix_value_decref(nix_c_context * context, nix_value * value);

/** @addtogroup value_manip Manipulating values
 * @brief Functions to inspect and change Nix language values, represented by nix_value.
 * @{
 */
/** @anchor getters
 * @name Getters
 */
/**@{*/
/** @brief Get value type
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @return type of nix value
 */
ValueType nix_get_type(nix_c_context * context, const nix_value * value);

/** @brief Get type name of value as defined in the evaluator
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @return type name, owned string
 * @todo way to free the result
 */
const char * nix_get_typename(nix_c_context * context, const nix_value * value);

/** @brief Get boolean value
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @return true or false, error info via context
 */
bool nix_get_bool(nix_c_context * context, const nix_value * value);

/** @brief Get the raw string
 *
 * This may contain placeholders.
 *
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @param[in] callback Called with the string value.
 * @param[in] user_data optional, arbitrary data, passed to the callback when it's called.
 * @return string
 * @return error code, NIX_OK on success.
 */
nix_err
nix_get_string(nix_c_context * context, const nix_value * value, nix_get_string_callback callback, void * user_data);

/** @brief Get path as string
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @return string, if the type is NIX_TYPE_PATH
 * @return NULL in case of error.
 */
const char * nix_get_path_string(nix_c_context * context, const nix_value * value);

/** @brief Get the length of a list
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @return length of list, error info via context
 */
unsigned int nix_get_list_size(nix_c_context * context, const nix_value * value);

/** @brief Get the element count of an attrset
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @return attrset element count, error info via context
 */
unsigned int nix_get_attrs_size(nix_c_context * context, const nix_value * value);

/** @brief Get float value in 64 bits
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @return float contents, error info via context
 */
double nix_get_float(nix_c_context * context, const nix_value * value);

/** @brief Get int value
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @return int contents, error info via context
 */
int64_t nix_get_int(nix_c_context * context, const nix_value * value);

/** @brief Get external reference
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @return reference to external, NULL in case of error
 */
ExternalValue * nix_get_external(nix_c_context * context, nix_value * value);

/** @brief Get the ix'th element of a list
 *
 * Owned by the GC. Use nix_gc_decref when you're done with the pointer
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @param[in] state nix evaluator state
 * @param[in] ix list element to get
 * @return value, NULL in case of errors
 */
nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix);

/** @brief Get an attr by name
 *
 * Owned by the GC. Use nix_gc_decref when you're done with the pointer
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @param[in] state nix evaluator state
 * @param[in] name attribute name
 * @return value, NULL in case of errors
 */
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);

/** @brief Check if an attribute name exists on a value
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @param[in] state nix evaluator state
 * @param[in] name attribute name
 * @return value, error info via context
 */
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);

/** @brief Get an attribute by index in the sorted bindings
 *
 * Also gives you the name.
 *
 * Owned by the GC. Use nix_gc_decref when you're done with the pointer
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @param[in] state nix evaluator state
 * @param[in] i attribute index
 * @param[out] name will store a pointer to the attribute name
 * @return value, NULL in case of errors
 */
nix_value * nix_get_attr_byidx(
    nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name);

/** @brief Get an attribute name by index in the sorted bindings
 *
 * Useful when you want the name but want to avoid evaluation.
 *
 * Owned by the nix EvalState
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value to inspect
 * @param[in] state nix evaluator state
 * @param[in] i attribute index
 * @return name, NULL in case of errors
 */
const char *
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i);

/**@}*/
/** @name Initializers
 *
 * Values are typically "returned" by initializing already allocated memory that serves as the return value.
 * For this reason, the construction of values is not tied their allocation.
 * Nix is a language with immutable values. Respect this property by only initializing Values once; and only initialize
 * Values that are meant to be initialized by you. Failing to adhere to these rules may lead to undefined behavior.
 */
/**@{*/
/** @brief Set boolean value
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] b the boolean value
 * @return error code, NIX_OK on success.
 */
nix_err nix_init_bool(nix_c_context * context, nix_value * value, bool b);

/** @brief Set a string
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] str the string, copied
 * @return error code, NIX_OK on success.
 */
nix_err nix_init_string(nix_c_context * context, nix_value * value, const char * str);

/** @brief Set a path
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] str the path string, copied
 * @return error code, NIX_OK on success.
 */
nix_err nix_init_path_string(nix_c_context * context, EvalState * s, nix_value * value, const char * str);

/** @brief Set a float
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] d the float, 64-bits
 * @return error code, NIX_OK on success.
 */
nix_err nix_init_float(nix_c_context * context, nix_value * value, double d);

/** @brief Set an int
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] i the int
 * @return error code, NIX_OK on success.
 */

nix_err nix_init_int(nix_c_context * context, nix_value * value, int64_t i);
/** @brief Set null
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @return error code, NIX_OK on success.
 */
nix_err nix_init_null(nix_c_context * context, nix_value * value);

/** @brief Set the value to a thunk that will perform a function application when needed.
 *
 * Thunks may be put into attribute sets and lists to perform some computation lazily; on demand.
 * However, note that in some places, a thunk must not be returned, such as in the return value of a PrimOp.
 * In such cases, you may use nix_value_call() instead (but note the different argument order).
 *
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] fn function to call
 * @param[in] arg argument to pass
 * @return error code, NIX_OK on successful initialization.
 * @see nix_value_call() for a similar function that performs the call immediately and only stores the return value.
 *      Note the different argument order.
 */
nix_err nix_init_apply(nix_c_context * context, nix_value * value, nix_value * fn, nix_value * arg);

/** @brief Set an external value
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] val the external value to set. Will be GC-referenced by the value.
 * @return error code, NIX_OK on success.
 */
nix_err nix_init_external(nix_c_context * context, nix_value * value, ExternalValue * val);

/** @brief Create a list from a list builder
 * @param[out] context Optional, stores error information
 * @param[in] list_builder list builder to use. Make sure to unref this afterwards.
 * @param[out] value Nix value to modify
 * @return error code, NIX_OK on success.
 */
nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, nix_value * value);

/** @brief Create a list builder
 * @param[out] context Optional, stores error information
 * @param[in] state nix evaluator state
 * @param[in] capacity how many bindings you'll add. Don't exceed.
 * @return owned reference to a list builder. Make sure to unref when you're done.
 */
ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state, size_t capacity);

/** @brief Insert bindings into a builder
 * @param[out] context Optional, stores error information
 * @param[in] list_builder ListBuilder to insert into
 * @param[in] index index to manipulate
 * @param[in] value value to insert
 * @return error code, NIX_OK on success.
 */
nix_err
nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, nix_value * value);

/** @brief Free a list builder
 *
 * Does not fail.
 * @param[in] list_builder The builder to free.
 */
void nix_list_builder_free(ListBuilder * list_builder);

/** @brief Create an attribute set from a bindings builder
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] b bindings builder to use. Make sure to unref this afterwards.
 * @return error code, NIX_OK on success.
 */
nix_err nix_make_attrs(nix_c_context * context, nix_value * value, BindingsBuilder * b);

/** @brief Set primop
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] op primop, will be gc-referenced by the value
 * @see nix_alloc_primop
 * @return error code, NIX_OK on success.
 */
nix_err nix_init_primop(nix_c_context * context, nix_value * value, PrimOp * op);
/** @brief Copy from another value
 * @param[out] context Optional, stores error information
 * @param[out] value Nix value to modify
 * @param[in] source value to copy from
 * @return error code, NIX_OK on success.
 */
nix_err nix_copy_value(nix_c_context * context, nix_value * value, const nix_value * source);
/**@}*/

/** @brief Create a bindings builder
* @param[out] context Optional, stores error information
* @param[in] state nix evaluator state
* @param[in] capacity how many bindings you'll add. Don't exceed.
* @return owned reference to a bindings builder. Make sure to unref when you're
done.
*/
BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState * state, size_t capacity);

/** @brief Insert bindings into a builder
 * @param[out] context Optional, stores error information
 * @param[in] builder BindingsBuilder to insert into
 * @param[in] name attribute name, only used for the duration of the call.
 * @param[in] value value to give the binding
 * @return error code, NIX_OK on success.
 */
nix_err
nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * builder, const char * name, nix_value * value);

/** @brief Free a bindings builder
 *
 * Does not fail.
 * @param[in] builder the builder to free
 */
void nix_bindings_builder_free(BindingsBuilder * builder);
/**@}*/

/** @brief Realise a string context.
 *
 * This will
 *  - realise the store paths referenced by the string's context, and
 *  - perform the replacement of placeholders.
 *  - create temporary garbage collection roots for the store paths, for
 *    the lifetime of the current process.
 *  - log to stderr
 *
 * @param[out] context Optional, stores error information
 * @param[in] value Nix value, which must be a string
 * @param[in] state Nix evaluator state
 * @param[in] isIFD If true, disallow derivation outputs if setting `allow-import-from-derivation` is false.
                    You should set this to true when this call is part of a primop.
                    You should set this to false when building for your application's purpose.
 * @return NULL if failed, are a new nix_realised_string, which must be freed with nix_realised_string_free
 */
nix_realised_string * nix_string_realise(nix_c_context * context, EvalState * state, nix_value * value, bool isIFD);

/** @brief Start of the string
 * @param[in] realised_string
 * @return pointer to the start of the string. It may not be null-terminated.
 */
const char * nix_realised_string_get_buffer_start(nix_realised_string * realised_string);

/** @brief Length of the string
 * @param[in] realised_string
 * @return length of the string in bytes
 */
size_t nix_realised_string_get_buffer_size(nix_realised_string * realised_string);

/** @brief Number of realised store paths
 * @param[in] realised_string
 * @return number of realised store paths that were referenced by the string via its context
 */
size_t nix_realised_string_get_store_path_count(nix_realised_string * realised_string);

/** @brief Get a store path. The store paths are stored in an arbitrary order.
 * @param[in] realised_string
 * @param[in] index index of the store path, must be less than the count
 * @return store path
 */
const StorePath * nix_realised_string_get_store_path(nix_realised_string * realised_string, size_t index);

/** @brief Free a realised string
 * @param[in] realised_string
 */
void nix_realised_string_free(nix_realised_string * realised_string);

// cffi end
#ifdef __cplusplus
}
#endif

/** @} */
#endif // NIX_API_VALUE_H
