#ifndef HOOK_H
#define HOOK_H
#include "config.h"
#include "run-command.h"
#include "string-list.h"
#include "strmap.h"
#include "strvec.h"

struct repository;

typedef void (*hook_data_free_fn)(void *data);
typedef void *(*hook_data_alloc_fn)(void *init_ctx);

/**
 * Represents a hook command to be run.
 * Hooks can be:
 * 1. "traditional" (found in the hooks directory)
 * 2. "configured" (defined in Git's configuration via hook.<friendly-name>.event).
 * The 'kind' field determines which part of the union 'u' is valid.
 */
struct hook {
	enum {
		HOOK_TRADITIONAL,
		HOOK_CONFIGURED,
	} kind;
	union {
		struct {
			const char *path;
		} traditional;
		struct {
			const char *friendly_name;
			const char *command;
			enum config_scope scope;
			bool disabled;
		} configured;
	} u;

	/**
	 * Opaque data pointer used to keep internal state across callback calls.
	 *
	 * It can be accessed directly via the third hook callback arg:
	 * struct ... *state = pp_task_cb;
	 *
	 * The caller is responsible for managing the memory for this data by
	 * providing alloc/free callbacks to `run_hooks_opt`.
	 *
	 * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
	 */
	void *feed_pipe_cb_data;

	/**
	 * Callback to free `feed_pipe_cb_data`.
	 *
	 * It is called automatically and points to the `feed_pipe_cb_data_free`
	 * provided via the `run_hook_opt` parameter.
	 */
	hook_data_free_fn data_free;
};

struct run_hooks_opt {
	/* Environment vars to be set for each hook */
	struct strvec env;

	/* Args to be passed to each hook */
	struct strvec args;

	/* Emit an error if the hook is missing */
	unsigned int error_if_missing:1;

	/**
	 *  Number of processes to parallelize across.
	 *
	 * If > 1, output will be buffered and de-interleaved (ungroup=0).
	 * If == 1, output will be real-time (ungroup=1).
	 */
	unsigned int jobs;

	/**
	 * An optional initial working directory for the hook,
	 * translates to "struct child_process"'s "dir" member.
	 */
	const char *dir;

	/**
	 * A pointer which if provided will be set to 1 or 0 depending
	 * on if a hook was started, regardless of whether or not that
	 * was successful. I.e. if the underlying start_command() was
	 * successful this will be set to 1.
	 *
	 * Used for avoiding TOCTOU races in code that would otherwise
	 * call hook_exist() after a "maybe hook run" to see if a hook
	 * was invoked.
	 */
	int *invoked_hook;

	/**
	 * Send the hook's stdout to stderr.
	 *
	 * This is the default behavior for all hooks except pre-push,
	 * which has separate stdout and stderr streams for backwards
	 * compatibility reasons.
	 */
	unsigned int stdout_to_stderr:1;

	/**
	 * Path to file which should be piped to stdin for each hook.
	 */
	const char *path_to_stdin;

	/**
	 * Callback used to incrementally feed a child hook stdin pipe.
	 *
	 * Useful especially if a hook consumes large quantities of data
	 * (e.g. a list of all refs in a client push), so feeding it via
	 * in-memory strings or slurping to/from files is inefficient.
	 * While the callback allows piecemeal writing, it can also be
	 * used for smaller inputs, where it gets called only once.
	 *
	 * Add hook callback initalization context to `feed_pipe_ctx`.
	 * Add hook callback internal state to `feed_pipe_cb_data`.
	 *
	 */
	feed_pipe_fn feed_pipe;

	/**
	 * Opaque data pointer used to pass context to `feed_pipe_fn`.
	 *
	 * It can be accessed via the second callback arg 'pp_cb':
	 * ((struct hook_cb_data *) pp_cb)->hook_cb->options->feed_pipe_ctx;
	 *
	 * The caller is responsible for managing the memory for this data.
	 * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
	 */
	void *feed_pipe_ctx;

	/**
	 * Some hooks need to create a fresh `feed_pipe_cb_data` internal state,
	 * so they can keep track of progress without affecting one another.
	 *
	 * If provided, this function will be called to alloc & initialize the
	 * `feed_pipe_cb_data` for each hook.
	 *
	 * The `feed_pipe_ctx` pointer can be used to pass initialization data.
	 */
	hook_data_alloc_fn feed_pipe_cb_data_alloc;

	/**
	 * Called to free the memory initialized by `feed_pipe_cb_data_alloc`.
	 *
	 * Must always be provided when `feed_pipe_cb_data_alloc` is provided.
	 */
	hook_data_free_fn feed_pipe_cb_data_free;
};

#define RUN_HOOKS_OPT_INIT { \
	.env = STRVEC_INIT, \
	.args = STRVEC_INIT, \
	.stdout_to_stderr = 1, \
	.jobs = 1, \
}

struct hook_cb_data {
	/* rc reflects the cumulative failure state */
	int rc;
	const char *hook_name;

	/**
	 * A list of hook commands/paths to run for the 'hook_name' event.
	 *
	 * The 'string' member of each item holds the path (for traditional hooks)
	 * or the unique friendly-name for hooks specified in configs.
	 * The 'util' member of each item points to the corresponding struct hook.
	 */
	struct string_list *hook_command_list;

	/* Iterator/cursor for the above list, pointing to the next hook to run. */
	size_t hook_to_run_index;

	struct run_hooks_opt *options;
};

/**
 * Provides a list of hook commands to run for the 'hookname' event.
 *
 * This function consolidates hooks from two sources:
 * 1. The config-based hooks (not yet implemented).
 * 2. The "traditional" hook found in the repository hooks directory
 *    (e.g., .git/hooks/pre-commit).
 *
 * The list is ordered by execution priority.
 *
 * The caller is responsible for freeing the memory of the returned list
 * using string_list_clear() and free().
 */
struct string_list *list_hooks(struct repository *r, const char *hookname,
			       struct run_hooks_opt *options);

/**
 * Frees a struct hook stored as the util pointer of a string_list_item.
 * Suitable for use as a string_list_clear_func_t callback.
 */
void hook_free(void *p, const char *str);

/**
 * Frees the hook configuration cache stored in `struct repository`.
 * Called by repo_clear().
 */
void hook_cache_clear(struct strmap *cache);

/**
 * Returns the path to the hook file, or NULL if the hook is missing
 * or disabled. Note that this points to static storage that will be
 * overwritten by further calls to find_hook and run_hook_*.
 */
const char *find_hook(struct repository *r, const char *name);

/**
 * A boolean version of find_hook()
 */
int hook_exists(struct repository *r, const char *hookname);

/**
 * Takes a `hook_name`, resolves it to a path with find_hook(), and
 * runs the hook for you with the options specified in "struct
 * run_hooks opt". Will free memory associated with the "struct run_hooks_opt".
 *
 * Returns the status code of the run hook, or a negative value on
 * error().
 */
int run_hooks_opt(struct repository *r, const char *hook_name,
		  struct run_hooks_opt *options);

/**
 * A wrapper for run_hooks_opt() which provides a dummy "struct
 * run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT".
 */
int run_hooks(struct repository *r, const char *hook_name);

/**
 * Like run_hooks(), a wrapper for run_hooks_opt().
 *
 * In addition to the wrapping behavior provided by run_hooks(), this
 * wrapper takes a list of strings terminated by a NULL
 * argument. These things will be used as positional arguments to the
 * hook. This function behaves like the old run_hook_le() API.
 */
LAST_ARG_MUST_BE_NULL
int run_hooks_l(struct repository *r, const char *hook_name, ...);
#endif
