#ifndef MVCC_H
#define MVCC_H

#include "git-compat-util.h"
#include "hash.h"

struct strbuf;
struct string_list;

/*
 * The manifest tracks the set of files that constitute one consistent
 * snapshot of the repository. Manifests are content-addressable: each
 * manifest body is named by the hex SHA-256 of its contents and stored
 * under "manifests/<sha256>".
 *
 * The mutable "manifest" pointer file holds the SHA-256 of the current
 * manifest body and is rewritten atomically whenever a new manifest is
 * activated.
 *
 * On-disk format
 * --------------
 *
 * A manifest body is a versioned, chunk-based binary file. See
 * Documentation/technical/mvcc-storage.adoc for the byte-level spec.
 * In broad strokes:
 *
 *   - An eight-byte header carries the magic "MVCC", a single-byte
 *     format version, a single-byte TOC entry count, a single-byte
 *     fixed PATH record width, and a single-byte identifier of the
 *     repository's object hash algorithm.
 *   - A table of contents (provided by the chunk-format library)
 *     enumerates the chunks that make up the body.
 *   - The "PATH" chunk holds the lex-sorted set of artifact paths
 *     relative to <mvcc_dir>. Each entry is a fixed-width
 *     NUL-padded record so the count is trivially derivable from
 *     the chunk size. This is the sole chunk an orchestrator
 *     (e.g. Gitaly) needs to enumerate.
 *   - The "OBJS" chunk encodes the contiguous PATH range holding the
 *     object-storage artifacts as a (start_index, count) pair.
 *   - The "REFS" chunk holds an explicit, stack-ordered list of PATH
 *     indices identifying the active reftable files. The order matters
 *     because newer reftables shadow older ones.
 *   - A trailing SHA-256 hash provides cheap parse-time corruption
 *     detection and serves as the manifest's stable content-addressed
 *     name.
 *
 * Unknown chunks are silently ignored, so new typed chunks can be added
 * without bumping the format version. Old readers continue to work.
 */

/*
 * In-memory view of a manifest body. Callers do not allocate or
 * initialize this struct directly; it is owned by `struct mvcc_storage`
 * and returned by `mvcc_storage_get_manifest`.
 */
struct mvcc_manifest {
	/*
	 * All artifact paths in the snapshot, lex-sorted (byte-wise) and
	 * stored verbatim. Each entry is a NUL-terminated relative path
	 * under the mvcc directory (e.g. "pack/<hash>.idx",
	 * "refs/<hash>.ref").
	 */
	const char **paths;
	size_t paths_nr;

	/*
	 * Contiguous PATH range holding object-storage artifacts.
	 * `objs_start` is the index of the first such entry; `objs_nr`
	 * is the count. Both are zero when no OBJS chunk was present.
	 */
	size_t objs_start;
	size_t objs_nr;

	/*
	 * Reftable PATH indices, in stack order (oldest first, newest
	 * last). The order is semantically significant: newer reftables
	 * shadow older ones for the same ref.
	 */
	const unsigned char *refs_indices_big_endian;
	size_t refs_nr;

	/*
	 * Opaque backing storage owned by the manifest. The buffer
	 * read from disk lives here; PATH and REFS pointers above are
	 * views into it.
	 */
	void *owned_data;
};

/*
 * Convenience constant for initializing an empty in-memory manifest.
 */
#define MVCC_MANIFEST_INIT { 0 }

/* Release memory associated with the given manifest. */
void mvcc_manifest_release(struct mvcc_manifest *manifest);

/*
 * Read and parse a manifest body file off disk into a freshly
 * allocated `struct mvcc_manifest`. Used by offline inspection tools.
 * Returns 0 on success, -1 on error (with a diagnostic emitted).
 *
 * The caller must release the manifest with `mvcc_manifest_release`.
 */
int mvcc_manifest_read_file(struct mvcc_manifest *manifest, const char *path);

/* Return the path of the n'th reference/object path. */
const char *mvcc_manifest_refs_path(const struct mvcc_manifest *manifest,
				    uint32_t idx);
const char *mvcc_manifest_objs_path(const struct mvcc_manifest *manifest,
				    uint32_t idx);

/*
 * Populate `obj_paths` (lex order irrelevant) and `ref_paths` (stack
 * order, oldest first) with the artifact paths from `manifest`. Used
 * by producers that want to build a new manifest as an extension of
 * an existing one.
 */
void mvcc_manifest_into_lists(const struct mvcc_manifest *manifest,
			      struct string_list *obj_paths,
			      struct string_list *ref_paths);

struct mvcc_storage {
	unsigned refcount;

	/*
	 * Whether the manifest pointer is pinned by GIT_MVCC_MANIFEST. A
	 * pinned storage is read-only: any attempt to write a manifest body
	 * or activate a new manifest dies. The pin is captured once at
	 * storage construction time.
	 */
	bool pinned;

	/*
	 * Repository common directory that this storage was created for. Used
	 * as the key in the global lookup map. The mvcc directory below derives
	 * from it.
	 */
	char *commondir;

	/*
	 * Local directory used as the on-disk store of artifacts that make
	 * up the repository. Layout:
	 *
	 *   <mvcc_dir>/manifest               mutable pointer file holding
	 *                                      the SHA-256 of the current
	 *                                      manifest body
	 *   <mvcc_dir>/manifests/<sha256>     immutable manifest bodies
	 *   <mvcc_dir>/pack/                  packs (.pack/.idx/.rev)
	 *   <mvcc_dir>/refs/<sha256>.ref      reftables (one per active table)
	 *
	 * All artifacts (packs, reftables, manifest bodies) are written
	 * straight into this directory. They are content-addressed, so
	 * concurrent writers producing the same artifact cannot collide,
	 * and unreferenced files left behind by aborted writes are
	 * reclaimed by the orchestrator's garbage collection rather than
	 * by Git itself.
	 */
	char *dir;

	/*
	 * Optional path to an external manifest pointer file. When set
	 * (typically via the GIT_MVCC_MANIFEST_PATH environment variable),
	 * this file -- rather than <mvcc_dir>/manifest -- is read and
	 * written by manifest resolution and activation.
	 *
	 * The orchestrator is responsible for creating this file before
	 * invoking Git (seeded with the hex SHA-256 of the base manifest)
	 * and for inspecting it after the RPC to discover the new manifest
	 * pointer that resulted from the write.
	 *
	 * NULL when no external manifest pointer has been requested; in
	 * that case <mvcc_dir>/manifest is used directly.
	 */
	char *manifest_pointer_path;

	/* The currently resolved manifest. */
	struct mvcc_manifest manifest;
	bool manifest_initialized;

	/*
	 * The hex SHA-256 of the manifest body that this storage handle
	 * believes is active. Used as the expected-old value for the
	 * compare-and-swap performed inside mvcc_storage_activate_manifest:
	 * if the on-disk pointer no longer matches this value at activation
	 * time, another writer has advanced the pointer underneath us and
	 * we must reject the activation rather than silently clobber.
	 *
	 * `current_version_set` is false when no manifest has been resolved
	 * yet (e.g. a freshly-initialised repo that has not gone through
	 * `create_on_disk`). In that case the CAS check is skipped because
	 * there is no prior value to compare against.
	 */
	char current_version[GIT_SHA256_HEXSZ + 1];
	bool current_version_set;
};

/*
 * Initialize and return the storage for the repository whose common
 * directory is `commondir`. If a storage has already been created for that
 * commondir, this returns the same storage instance (refcount bumped). This
 * makes the storage a shared resource for backends that want to use the
 * same underlying cache: any number of MVCC extensions configured against
 * the same repository will resolve to a single handle and therefore a
 * single in-memory manifest view.
 *
 * The mvcc directory is always `<commondir>/mvcc`. The location of
 * the external manifest pointer file, if any, is taken from the
 * GIT_MVCC_MANIFEST_PATH environment variable; when set, the file must
 * already exist and must be a non-empty regular file naming the base
 * manifest to chain on top of. Both invariants are the caller's
 * responsibility (typically Gitaly's) to establish before invoking Git.
 *
 * When the GIT_MVCC_MANIFEST environment variable is set, the resulting
 * storage handle is marked read-only: any subsequent attempt to write a
 * manifest body or activate a new manifest dies.
 *
 * Dies when initializing the storage fails.
 */
struct mvcc_storage *mvcc_storage_get(const char *commondir);
void mvcc_storage_release(struct mvcc_storage *storage);

/*
 * Kind of mvcc file we're dealing with. Each file has a corresponding
 * basename within the mvcc dir.
 */
enum mvcc_kind {
	MVCC_UNKNOWN = 0,
	MVCC_PACKS,
	MVCC_REFS,
	MVCC_MANIFESTS,
};

/*
 * Compute the path of an artifact under the storage's mvcc directory.
 * `kind` is the file's basename within that subdirectory, or NULL to get
 * just the subdirectory path. The result is appended to `out`.
 *
 * Artifacts are content-addressed, so concurrent writers producing the
 * same artifact cannot collide and there is no need to keep
 * newly-written files apart from the existing set. Callers that
 * require the file to exist should `access(2)` the returned path.
 */
void mvcc_storage_path(struct mvcc_storage *storage, enum mvcc_kind kind,
		       const char *name, struct strbuf *out);

/*
 * Get the current manifest from the storage. The manifest does not need to
 * be released, as it is owned by the storage itself.
 *
 * Note that the manifest is cached for the lifetime of the storage object:
 * once it has been resolved (or written to via mvcc_storage_activate_manifest()),
 * this function returns the cached value. Callers that need a fresh view
 * across processes must release and re-acquire the storage.
 */
const struct mvcc_manifest *mvcc_storage_get_manifest(struct mvcc_storage *storage);

/*
 * Write the manifest body to its content-addressed location under
 * "<mvcc_dir>/manifests/<sha256>".
 *
 * `obj_paths` lists object-storage artifact paths relative to the cache
 * directory (e.g. "pack/<hash>.idx"); their order is irrelevant.
 * `ref_paths` lists reftable artifact paths in stack order (oldest first,
 * newest last); the order is semantically significant.
 *
 * Paths must be non-empty, must not start with "/", must not contain a
 * ".." component, and must not contain empty components. Duplicate paths
 * across the union of `obj_paths` and `ref_paths` are rejected.
 *
 * The resulting hash of the manifest is written into `version_hex_out`
 * (if not NULL), which must have room for at least GIT_SHA256_HEXSZ + 1
 * bytes.
 *
 * This does NOT update the manifest pointer; use
 * mvcc_storage_activate_manifest() to make the manifest current.
 */
int mvcc_storage_write_manifest(struct mvcc_storage *storage,
				const struct string_list *obj_paths,
				const struct string_list *ref_paths,
				const struct git_hash_algo *object_hash,
				char *version_hex_out);

/*
 * Write the manifest body and atomically activate it by rewriting the
 * manifest pointer file (either <mvcc_dir>/manifest or the external
 * pointer file named by GIT_MVCC_MANIFEST_PATH, whichever is in effect).
 * The pointer is updated using Git's lockfile machinery, so concurrent
 * writers on the same filesystem are mutually excluded.
 *
 * On success, the storage's cached manifest is updated so that subsequent
 * reads in this process see the new state.
 */
int mvcc_storage_activate_manifest(struct mvcc_storage *storage,
				   const struct string_list *obj_paths,
				   const struct string_list *ref_paths,
				   const struct git_hash_algo *object_hash);

#endif
