#ifndef NIX_API_EXPR_H
#define NIX_API_EXPR_H
/** @defgroup libexpr libexpr
 * @brief Bindings to the Nix language evaluator
 *
 * See *[Embedding the Nix Evaluator](@ref nix_evaluator_example)* for an example.
 * @{
 */
/** @file
 * @brief Main entry for the libexpr C bindings
 */

#include "nix_api_store.h"
#include "nix_api_util.h"
#include <stddef.h>

#ifndef __has_c_attribute
#  define __has_c_attribute(x) 0
#endif

#if __has_c_attribute(deprecated)
#  define NIX_DEPRECATED(msg) [[deprecated(msg)]]
#else
#  define NIX_DEPRECATED(msg)
#endif

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

// Type definitions
/**
 * @brief Represents a state of the Nix language evaluator.
 *
 * Multiple states can be created for multi-threaded
 * operation.
 * @struct EvalState
 * @see nix_state_create
 */
typedef struct EvalState EvalState; // nix::EvalState

/** @brief A Nix language value, or thunk that may evaluate to a value.
 *
 * Values are the primary objects manipulated in the Nix language.
 * They are considered to be immutable from a user's perspective, but the process of evaluating a value changes its
 * ValueType if it was a thunk. After a value has been evaluated, its ValueType does not change.
 *
 * Evaluation in this context refers to the process of evaluating a single value object, also called "forcing" the
 * value; see `nix_value_force`.
 *
 * The evaluator manages its own memory, but your use of the C API must follow the reference counting rules.
 *
 * @see value_manip
 * @see nix_value_incref, nix_value_decref
 */
typedef struct nix_value nix_value;
NIX_DEPRECATED("use nix_value instead") typedef nix_value Value;

// Function prototypes
/**
 * @brief Initialize the Nix language evaluator.
 *
 * This function must be called at least once,
 * at some point before constructing a EvalState for the first time.
 * This function can be called multiple times, and is idempotent.
 *
 * @param[out] context Optional, stores error information
 * @return NIX_OK if the initialization was successful, an error code otherwise.
 */
nix_err nix_libexpr_init(nix_c_context * context);

/**
 * @brief Parses and evaluates a Nix expression from a string.
 *
 * @param[out] context Optional, stores error information
 * @param[in] state The state of the evaluation.
 * @param[in] expr The Nix expression to parse.
 * @param[in] path The file path to associate with the expression.
 * This is required for expressions that contain relative paths (such as `./.`) that are resolved relative to the given
 * directory.
 * @param[out] value The result of the evaluation. You must allocate this
 * yourself.
 * @return NIX_OK if the evaluation was successful, an error code otherwise.
 */
nix_err nix_expr_eval_from_string(
    nix_c_context * context, EvalState * state, const char * expr, const char * path, nix_value * value);

/**
 * @brief Calls a Nix function with an argument.
 *
 * @param[out] context Optional, stores error information
 * @param[in] state The state of the evaluation.
 * @param[in] fn The Nix function to call.
 * @param[in] arg The argument to pass to the function.
 * @param[out] value The result of the function call.
 * @return NIX_OK if the function call was successful, an error code otherwise.
 * @see nix_init_apply() for a similar function that does not performs the call immediately, but stores it as a thunk.
 *      Note the different argument order.
 */
nix_err nix_value_call(nix_c_context * context, EvalState * state, nix_value * fn, nix_value * arg, nix_value * value);

/**
 * @brief Calls a Nix function with multiple arguments.
 *
 * Technically these are functions that return functions. It is common for Nix
 * functions to be curried, so this function is useful for calling them.
 *
 * @param[out] context Optional, stores error information
 * @param[in] state The state of the evaluation.
 * @param[in] fn The Nix function to call.
 * @param[in] nargs The number of arguments.
 * @param[in] args The arguments to pass to the function.
 * @param[out] value The result of the function call.
 *
 * @see nix_value_call     For the single argument primitive.
 * @see NIX_VALUE_CALL           For a macro that wraps this function for convenience.
 */
nix_err nix_value_call_multi(
    nix_c_context * context, EvalState * state, nix_value * fn, size_t nargs, nix_value ** args, nix_value * value);

/**
 * @brief Calls a Nix function with multiple arguments.
 *
 * Technically these are functions that return functions. It is common for Nix
 * functions to be curried, so this function is useful for calling them.
 *
 * @param[out] context Optional, stores error information
 * @param[in] state The state of the evaluation.
 * @param[out] value The result of the function call.
 * @param[in] fn The Nix function to call.
 * @param[in] args The arguments to pass to the function.
 *
 * @see nix_value_call_multi
 */
#define NIX_VALUE_CALL(context, state, value, fn, ...)                      \
    do {                                                                    \
        nix_value * args_array[] = {__VA_ARGS__};                           \
        size_t nargs = sizeof(args_array) / sizeof(args_array[0]);          \
        nix_value_call_multi(context, state, fn, nargs, args_array, value); \
    } while (0)

/**
 * @brief Forces the evaluation of a Nix value.
 *
 * The Nix interpreter is lazy, and not-yet-evaluated values can be
 * of type NIX_TYPE_THUNK instead of their actual value.
 *
 * This function mutates such a `nix_value`, so that, if successful, it has its final type.
 *
 * @param[out] context Optional, stores error information
 * @param[in] state The state of the evaluation.
 * @param[in,out] value The Nix value to force.
 * @post value is not of type NIX_TYPE_THUNK
 * @return NIX_OK if the force operation was successful, an error code
 * otherwise.
 */
nix_err nix_value_force(nix_c_context * context, EvalState * state, nix_value * value);

/**
 * @brief Forces the deep evaluation of a Nix value.
 *
 * Recursively calls nix_value_force
 *
 * @see nix_value_force
 * @warning Calling this function on a recursive data structure will cause a
 * stack overflow.
 * @param[out] context Optional, stores error information
 * @param[in] state The state of the evaluation.
 * @param[in,out] value The Nix value to force.
 * @return NIX_OK if the deep force operation was successful, an error code
 * otherwise.
 */
nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_value * value);

/**
 * @brief Create a new Nix language evaluator state.
 *
 * @param[out] context Optional, stores error information
 * @param[in] lookupPath Null-terminated array of strings corresponding to entries in NIX_PATH.
 * @param[in] store The Nix store to use.
 * @return A new Nix state or NULL on failure.
 */
EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath, Store * store);

/**
 * @brief Frees a Nix state.
 *
 * Does not fail.
 *
 * @param[in] state The state to free.
 */
void nix_state_free(EvalState * state);

/** @addtogroup GC
 * @brief Reference counting and garbage collector operations
 *
 * The Nix language evaluator uses a garbage collector. To ease C interop, we implement
 * a reference counting scheme, where objects will be deallocated
 * when there are no references from the Nix side, and the reference count kept
 * by the C API reaches `0`.
 *
 * Functions returning a garbage-collected object will automatically increase
 * the refcount for you. You should make sure to call `nix_gc_decref` when
 * you're done with a value returned by the evaluator.
 * @{
 */

// TODO: Deprecate nix_gc_incref in favor of the type-specific reference counting functions?
//       e.g. nix_value_incref.
//       It gives implementors more flexibility, and adds safety, so that generated
//       bindings can be used without fighting the host type system (where applicable).
/**
 * @brief Increment the garbage collector reference counter for the given object.
 *
 * The Nix language evaluator C API keeps track of alive objects by reference counting.
 * When you're done with a refcounted pointer, call nix_gc_decref().
 *
 * @param[out] context Optional, stores error information
 * @param[in] object The object to keep alive
 */
nix_err nix_gc_incref(nix_c_context * context, const void * object);
/**
 * @brief Decrement the garbage collector reference counter for the given object
 *
 * @param[out] context Optional, stores error information
 * @param[in] object The object to stop referencing
 */
nix_err nix_gc_decref(nix_c_context * context, const void * object);

/**
 * @brief Trigger the garbage collector manually
 *
 * You should not need to do this, but it can be useful for debugging.
 */
void nix_gc_now();

/**
 * @brief Register a callback that gets called when the object is garbage
 * collected.
 * @note Objects can only have a single finalizer. This function overwrites existing values
 * silently.
 * @param[in] obj the object to watch
 * @param[in] cd the data to pass to the finalizer
 * @param[in] finalizer the callback function, called with obj and cd
 */
void nix_gc_register_finalizer(void * obj, void * cd, void (*finalizer)(void * obj, void * cd));

/** @} */
// cffi end
#ifdef __cplusplus
}
#endif

/** @} */

#endif // NIX_API_EXPR_H
