// SPDX-License-Identifier: GPL-2.0-only
/*
 *  ppm/include/ppm/transaction.h
 *
 *  Copyright (C) 2026 Project Tick
 */

#ifndef PPM_TRANSACTION_H
#define PPM_TRANSACTION_H

#include <ppm/error.h>
#include <ppm/store.h>
#include <ppm/lockfile.h>

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Transaction — the atomic unit of state change in ppm.
 *
 * Profile model (Nix-inspired):
 *   <store>/profiles/<name>/gen-<n>/       a snapshot of installed packages
 *   <store>/profiles/<name>/current        symlink to the active generation
 *
 * Each call to `ppm_txn_commit` creates a new `gen-<n>` directory and
 * atomically replaces `current` to point at it. Old generations remain
 * on disk until `ppm gc` removes them, which is what enables rollbacks.
 *
 * Crash safety is provided by a write-ahead journal living at
 *   <store>/tmp/<txn-id>.journal
 * The journal records every staged package and the path of the new
 * generation directory. A journal that lacks a final "ready" record
 * means the previous run crashed mid-commit; recovery removes the
 * partial generation. A journal that has "ready" but the rename did
 * not complete is replayed to flip the symlink.
 *
 * Threading: at most one transaction per profile at a time. Concurrent
 * processes are serialised by a file lock on the profile directory.
 */

typedef struct ppm_txn ppm_txn_t;

ppm_error_t ppm_txn_begin (ppm_store_t *store, const char *profile, ppm_txn_t **out);

/*
 * Stage a package version as part of this transaction. The package must
 * already be present in the store (or be ingested by the caller before
 * commit). Staging only records the intent in the journal and in the
 * pending generation manifest.
 */
ppm_error_t ppm_txn_stage (ppm_txn_t *txn, const ppm_lock_entry_t *entry);

/* Commit — atomically replaces the active generation symlink. */
ppm_error_t ppm_txn_commit(ppm_txn_t *txn);

/*
 * Abort — discards every staged change and removes the pending
 * generation directory. Safe to call multiple times; safe to call
 * after a partial commit (it becomes a no-op).
 */
void        ppm_txn_abort (ppm_txn_t *txn);

/*
 * Replay or roll back any journals left behind by previous crashed runs
 * for the given profile. Called automatically at the start of every
 * `ppm_txn_begin`. Idempotent.
 */
ppm_error_t ppm_txn_recover(ppm_store_t *store, const char *profile);

/*
 * Return the absolute path of the active generation for `profile`, or
 * NULL if the profile has no generations yet. Caller frees.
 */
ppm_error_t ppm_txn_current(ppm_store_t *store, const char *profile,
                            char **out_path);

#ifdef __cplusplus
}
#endif

#endif /* PPM_TRANSACTION_H */
