/*
 * kglite-c — C ABI for the kglite knowledge graph engine.
 *
 * Generated by cbindgen. Do NOT edit by hand. To regenerate:
 *   cargo build -p kglite-c
 * (the crate's build.rs runs cbindgen automatically).
 *
 * Conventions: see docs/rust/c-abi.md in the kglite repo.
 *   https://github.com/kkollsga/kglite/blob/main/docs/rust/c-abi.md
 */

#ifndef KGLITE_H_INCLUDED
#define KGLITE_H_INCLUDED

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

/**
 * C-ABI-side error code. Variants 1-16 map 1:1 to
 * [`kglite::api::KgErrorCode`]; variants 100+ are C-ABI-specific
 * (invalid UTF-8 at the boundary, null pointer, OOM — conditions
 * that don't have a corresponding `KgErrorCode` because they
 * can't arise from inside the engine).
 */
enum KgliteStatusCode {
  KGLITE_STATUS_CODE_OK = 0,
  KGLITE_STATUS_CODE_CYPHER_SYNTAX = 1,
  KGLITE_STATUS_CODE_CYPHER_TIMEOUT = 2,
  KGLITE_STATUS_CODE_CYPHER_EXECUTION = 3,
  KGLITE_STATUS_CODE_CYPHER_TYPE_MISMATCH = 4,
  KGLITE_STATUS_CODE_SCHEMA = 5,
  KGLITE_STATUS_CODE_VALIDATION = 6,
  KGLITE_STATUS_CODE_EXPR = 7,
  KGLITE_STATUS_CODE_NODE_NOT_FOUND = 8,
  KGLITE_STATUS_CODE_CONNECTION_NOT_FOUND = 9,
  KGLITE_STATUS_CODE_PROPERTY_NOT_FOUND = 10,
  KGLITE_STATUS_CODE_FILE_NOT_FOUND = 11,
  KGLITE_STATUS_CODE_FILE_FORMAT = 12,
  KGLITE_STATUS_CODE_FILE_IO = 13,
  KGLITE_STATUS_CODE_INVALID_ARGUMENT = 14,
  KGLITE_STATUS_CODE_MISSING_ARGUMENT = 15,
  KGLITE_STATUS_CODE_INTERNAL = 16,
  /**
   * The query was cooperatively cancelled (a binding flipped the
   * cancel flag). Appended here (not renumbered into core's
   * declaration position) to keep the existing discriminants stable
   * across this ABI major version.
   */
  KGLITE_STATUS_CODE_CANCELLED = 17,
  /**
   * A string argument failed UTF-8 validation. The C-side
   * caller passed a `*const c_char` whose bytes didn't decode
   * as UTF-8 — typically a corrupted buffer or a non-UTF-8
   * locale string. kglite is UTF-8 throughout.
   */
  KGLITE_STATUS_CODE_INVALID_UTF8 = 100,
  /**
   * A required pointer argument was null. The function
   * can't proceed; check your call site.
   */
  KGLITE_STATUS_CODE_NULL_POINTER = 101,
};
typedef uint32_t KgliteStatusCode;

/**
 * The ABI version that this build of `kglite-c` exposes. Derived at
 * compile time from the crate's package version (`CARGO_PKG_VERSION_*`),
 * so it tracks the engine version automatically.
 */
typedef struct KgliteAbiVersion {
  uint32_t major;
  uint32_t minor;
  uint32_t patch;
} KgliteAbiVersion;

/**
 * Rust-heap statistics from kglite's tracking allocator.
 */
typedef struct KgMemStats {
  /**
   * Current live Rust-heap bytes (allocated minus freed).
   */
  uint64_t current_bytes;
  /**
   * Peak live Rust-heap bytes since process start.
   */
  uint64_t peak_bytes;
  /**
   * Total number of allocations since process start (monotonic).
   */
  uint64_t total_allocs;
} KgMemStats;

#if defined(KGLITE_FEATURE_SEC)
/**
 * Opaque handle for a SEC HTTP client (rate-limited, user-agent
 * validating). See [`KgliteGraph`](crate::KgliteGraph) for the
 * rationale on the empty `#[repr(C)]` facade pattern.
 */
typedef struct KgliteSecClient {
  uint8_t _opaque[0];
} KgliteSecClient;
#endif

/**
 * Opaque handle for an embedder. See
 * [`KgliteGraph`](crate::KgliteGraph) for the rationale on the
 * empty `#[repr(C)]` facade pattern — cbindgen renders only a
 * forward declaration; the actual state lives in
 * [`EmbedderState`].
 */
typedef struct KgliteEmbedder {
  uint8_t _opaque[0];
} KgliteEmbedder;

/**
 * Opaque handle for a session. See [`KgliteGraph`](crate::KgliteGraph)
 * for the rationale on the empty `#[repr(C)]` facade pattern.
 */
typedef struct KgliteSession {
  uint8_t _opaque[0];
} KgliteSession;

/**
 * Opaque handle for a knowledge graph. The C-side caller only
 * ever sees `KgliteGraph*`; allocation, deallocation, and field
 * access happen inside `kglite-c`.
 *
 * cbindgen sees the `#[repr(C)]` empty struct and renders only a
 * forward declaration in `kglite.h`. The actual state lives in
 * the private [`GraphState`] sidecar: every `*mut KgliteGraph`
 * the C side holds is really a `*mut GraphState` cast through
 * the opaque facade.
 */
typedef struct KgliteGraph {
  uint8_t _opaque[0];
} KgliteGraph;

/**
 * Opaque handle for a Cypher result. See
 * [`KgliteGraph`](crate::KgliteGraph) for the rationale on the
 * empty `#[repr(C)]` facade pattern — cbindgen renders only a
 * forward declaration; the actual state lives in [`ResultState`].
 */
typedef struct KgliteCypherResult {
  uint8_t _opaque[0];
} KgliteCypherResult;

/**
 * Return the C ABI version this library was built against.
 * Bindings should call this on startup and refuse to proceed if
 * the major version doesn't match what they were compiled
 * against — a mismatched major risks segfaults from changed
 * struct layouts or removed functions.
 *
 * Conventions within a major version: additive only (new
 * functions, new status codes, new opaque types). Existing
 * function signatures and struct layouts never change.
 *
 * # Examples
 *
 * ```c
 * KgliteAbiVersion v = kglite_abi_version();
 * if (v.major != KGLITE_EXPECTED_MAJOR) {
 *     fprintf(stderr, "kglite ABI mismatch: expected %u.x, got %u.%u.%u\n",
 *                     KGLITE_EXPECTED_MAJOR, v.major, v.minor, v.patch);
 *     return 1;
 * }
 * ```
 */
 struct KgliteAbiVersion kglite_abi_version(void);

/**
 * Return current Rust-heap statistics from kglite's tracking allocator.
 * Counts only allocations through the Rust global allocator — the host
 * runtime's own heap is separate. Useful for a binding to surface
 * kglite's memory footprint in its own metrics.
 */
 struct KgMemStats kglite_memory_stats(void);

#if defined(KGLITE_FEATURE_SODIR)
/**
 * Fetch all Sodir (Norwegian Continental Shelf) datasets the
 * caller asks for. Sync wrapper around the engine's
 * [`fetch_all_blocking`] entry point — spins up a single-thread
 * tokio runtime per call, fetches missing/stale CSVs from the
 * ArcGIS FactMaps REST API, applies preprocessing (FK fixups),
 * returns a report.
 *
 * # Arguments
 *
 * - `workdir_path` (in, borrowed): directory under which CSVs
 *   land. Layout: `<root>/csv/<dataset>.csv`,
 *   `<root>/index.json`. Created on first use; subsequent
 *   calls reuse the layout.
 * - `datasets_json` (in, borrowed): JSON array of dataset
 *   stem names the caller wants — e.g.
 *   `["field", "wellbore_exploration", "production_profile"]`.
 *   Must not be null. Pass `"[]"` for no datasets.
 * - `index_cooldown_days` (in): how long the workdir's
 *   `index.json` is trusted before re-probing the catalog.
 *   Wheel default: 7.
 * - `dataset_cooldown_days` (in): how long an already-fetched
 *   CSV is trusted before re-fetching. Wheel default: 30.
 * - `concurrency` (in): max parallel HTTP fetches. Wheel
 *   default: 10.
 * - `out_report_json` (out, owned): on success, set to a
 *   JSON object string with the report fields. Caller must
 *   free via [`kglite_free_string`](crate::kglite_free_string).
 *   Shape:
 *   ```json
 *   {
 *     "refresh": {
 *       "fetched": ["..."],
 *       "unchanged": ["..."],
 *       "user_supplied": ["..."],
 *       "cached": ["..."],
 *       "unfetchable": ["..."],
 *       "errors": [["stem", "message"], ...]
 *     },
 *     "preprocess": {
 *       "petreg_licence_pk": null | <int>,
 *       "seismic_progress_fk": null | <int>,
 *       "chrono_parent_fk": null | <int>,
 *       "announced_block_fk": null | <int>
 *     }
 *   }
 *   ```
 * - `out_error_msg` (out, owned, may be null): on failure,
 *   set to an owned error message string. Caller must free
 *   via [`kglite_free_string`](crate::kglite_free_string).
 *
 * # Errors
 *
 * - `KGLITE_STATUS_CODE_NULL_POINTER` — required pointer is null
 * - `KGLITE_STATUS_CODE_INVALID_UTF8` — input string isn't valid UTF-8
 * - `KGLITE_STATUS_CODE_INVALID_ARGUMENT` — `datasets_json` isn't a
 *   JSON array of strings
 * - `KGLITE_STATUS_CODE_FILE_IO` — workdir creation or CSV write failed
 * - `KGLITE_STATUS_CODE_INTERNAL` — REST API call failed, tokio
 *   runtime build failed, or other engine-level error
 *
 * # Safety
 *
 * `workdir_path` and `datasets_json` must be null-terminated
 * UTF-8 strings. `out_report_json` must be a valid writable
 * pointer to a `*const c_char` slot.
 */

KgliteStatusCode kglite_datasets_sodir_fetch_all(const char *workdir_path,
                                                 const char *datasets_json,
                                                 int64_t index_cooldown_days,
                                                 int64_t dataset_cooldown_days,
                                                 uintptr_t concurrency,
                                                 const char **out_report_json,
                                                 const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_SEC)
/**
 * Construct a SEC HTTP client. The `user_agent` is mandatory and
 * must be non-empty — SEC's fair-access policy requires a
 * descriptive identifier with contact info (e.g.
 * `"Acme Corp research@example.com"`).
 *
 * # Arguments
 *
 * - `user_agent` (in, borrowed): UTF-8 string, non-empty after trim.
 * - `out_client` (out, owned): on success, set to a client handle.
 *   Caller must free via [`kglite_datasets_sec_client_free`].
 * - `out_error_msg` (out, owned, may be null): on failure, set to
 *   an error message string.
 *
 * # Errors
 *
 * - `KGLITE_STATUS_CODE_NULL_POINTER` — required pointer is null
 * - `KGLITE_STATUS_CODE_INVALID_UTF8` — `user_agent` isn't valid UTF-8
 * - `KGLITE_STATUS_CODE_INVALID_ARGUMENT` — `user_agent` is empty
 *   after trim
 *
 * # Safety
 *
 * `user_agent` must be null-terminated UTF-8.
 * `out_client` must be a valid writable pointer.
 */

KgliteStatusCode kglite_datasets_sec_client_new(const char *user_agent,
                                                struct KgliteSecClient **out_client,
                                                const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_SEC)
/**
 * Free a SEC client handle. Idempotent on null.
 *
 * # Safety
 *
 * `client` must be either null or a valid pointer previously
 * returned by [`kglite_datasets_sec_client_new`].
 */
 void kglite_datasets_sec_client_free(struct KgliteSecClient *client);
#endif

#if defined(KGLITE_FEATURE_SEC)
/**
 * Fetch the quarterly `master.idx` files covering a year range,
 * landing them in `<workdir>/raw/master_idx/`. Returns counts of
 * files written and files skipped (already present).
 *
 * # Arguments
 *
 * - `client` (in, borrowed): SEC HTTP client.
 * - `workdir_path` (in, borrowed): root for the layout.
 * - `year_start`, `year_end` (in): inclusive year range. EDGAR's
 *   earliest quarter is 1993 Q3; quarters before that are skipped.
 * - `current_year`, `current_quarter` (in): the "now" reference
 *   so the fetcher knows to skip future quarters. Callers
 *   typically pass the system clock's year + (month/3 + 1).
 * - `out_pair_json` (out, owned): on success, set to a 2-element
 *   JSON array `[fetched_count, skipped_count]`.
 * - `out_error_msg` (out, owned, may be null).
 *
 * # Safety
 *
 * `client` and the string args must be valid. `out_pair_json`
 * must be a valid writable pointer.
 */

KgliteStatusCode kglite_datasets_sec_fetch_quarterly_master_idx(const struct KgliteSecClient *client,
                                                                const char *workdir_path,
                                                                uint16_t year_start,
                                                                uint16_t year_end,
                                                                uint16_t current_year,
                                                                uint8_t current_quarter,
                                                                const char **out_pair_json,
                                                                const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_SEC)
/**
 * Fetch the bulk-download `submissions.zip` (all companies' filing
 * metadata in one archive). Lands at
 * `<workdir>/raw/bulk/submissions.zip`. Returns `true` if a fresh
 * download landed (mtime older than `staleness_hours`, or
 * `force_refetch`), `false` if the existing file was reused.
 *
 * # Arguments
 *
 * - `client` (in, borrowed).
 * - `workdir_path` (in, borrowed).
 * - `staleness_hours` (in): how stale the cached zip can be before
 *   re-fetching. SEC publishes nightly so 24 is a reasonable default.
 * - `force_refetch` (in): non-zero forces re-download regardless of
 *   staleness.
 * - `out_fetched` (out): set to 1 if downloaded fresh, 0 if reused.
 * - `out_error_msg` (out, owned, may be null).
 *
 * # Safety
 *
 * `client` and `workdir_path` must be valid. `out_fetched` must be
 * a valid writable pointer.
 */

KgliteStatusCode kglite_datasets_sec_fetch_submissions_bulk(const struct KgliteSecClient *client,
                                                            const char *workdir_path,
                                                            uint64_t staleness_hours,
                                                            uint8_t force_refetch,
                                                            uint8_t *out_fetched,
                                                            const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_SEC)
/**
 * Fetch the `company_tickers.json` mapping (TICKER → CIK).
 * Lands at `<workdir>/raw/company_tickers.json`. Returns `true`
 * if a fresh download landed.
 *
 * # Safety
 *
 * Same shape as [`kglite_datasets_sec_fetch_submissions_bulk`].
 */

KgliteStatusCode kglite_datasets_sec_fetch_company_tickers(const struct KgliteSecClient *client,
                                                           const char *workdir_path,
                                                           uint8_t force_refetch,
                                                           uint8_t *out_fetched,
                                                           const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_SEC)
/**
 * Fetch the XBRL `companyfacts/CIK<cik>.json` file (a single
 * company's full XBRL fact history). Lands at
 * `<workdir>/raw/company_facts/CIK<cik>.json`. Returns `true` if
 * a fresh download landed.
 *
 * # Arguments
 *
 * - `client`, `workdir_path`: see other fetchers.
 * - `cik` (in): the company's CIK as an integer (no zero-padding).
 * - `force_refetch` (in): non-zero forces re-download.
 *
 * # Safety
 *
 * Same shape as the other fetchers.
 */

KgliteStatusCode kglite_datasets_sec_fetch_company_facts(const struct KgliteSecClient *client,
                                                         const char *workdir_path,
                                                         uint64_t cik,
                                                         uint8_t force_refetch,
                                                         uint8_t *out_fetched,
                                                         const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_SEC)
/**
 * Resolve a list of user-supplied form-type strings into the
 * per-filing-fetcher buckets needed to cover them, plus a list of
 * unrecognized form types the caller should warn about.
 *
 * Pure-CPU — no I/O, no client. Mirrors
 * `kglite::api::datasets::sec::resolve_fetch_buckets`.
 *
 * # Arguments
 *
 * - `form_types_json` (in, borrowed): JSON array of form-type strings
 *   (e.g. `["10-K", "4", "13F-HR"]`), or the literal `"null"`
 *   for "use the lean default set".
 * - `out_active_json` (out, owned): JSON array of bucket name
 *   strings, e.g. `["form4", "13f"]`.
 * - `out_unmatched_json` (out, owned): JSON array of strings that
 *   didn't match any bucket.
 * - `out_error_msg` (out, owned, may be null).
 *
 * # Errors
 *
 * - `KGLITE_STATUS_CODE_INVALID_ARGUMENT` — `form_types_json` isn't
 *   a JSON array of strings (or null).
 *
 * # Safety
 *
 * All input strings must be null-terminated UTF-8.
 */

KgliteStatusCode kglite_datasets_sec_resolve_fetch_buckets(const char *form_types_json,
                                                           const char **out_active_json,
                                                           const char **out_unmatched_json,
                                                           const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_SEC)
/**
 * Parse SEC's `company_tickers.json` shape into a TICKER → CIK
 * map. Pure-CPU, no I/O. Returns the map as JSON object string.
 *
 * # Arguments
 *
 * - `tickers_json` (in, borrowed): the raw JSON from SEC's published
 *   `company_tickers.json`.
 * - `out_map_json` (out, owned): JSON object `{"AAPL": 320193, ...}`.
 * - `out_error_msg` (out, owned, may be null).
 *
 * # Errors
 *
 * - `KGLITE_STATUS_CODE_INVALID_ARGUMENT` — `tickers_json` isn't
 *   valid JSON.
 *
 * # Safety
 *
 * `tickers_json` must be null-terminated UTF-8.
 */

KgliteStatusCode kglite_datasets_sec_parse_tickers_json(const char *tickers_json,
                                                        const char **out_map_json,
                                                        const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_SEC)
/**
 * Run the SEC extract pipeline — reads `<workdir>/raw/` (the
 * downloaded artifacts) and produces `<workdir>/processed/`
 * CSVs (`company.csv`, `filing_index.csv`, `form4_transaction.csv`,
 * `holding.csv`, etc.).
 *
 * # Arguments
 *
 * - `workdir_path` (in, borrowed).
 * - `slice_json` (in, borrowed, may be null): JSON object with
 *   optional filters:
 *   ```json
 *   {
 *     "cik_list":   [320193, 789019],
 *     "form_types": ["10-K", "10-Q"],
 *     "year_range": [2020, 2024]
 *   }
 *   ```
 *   Any missing / null field means "no restriction on that axis".
 *   Pass null or `"{}"` for fully unrestricted.
 * - `force` (in): non-zero re-runs even when the
 *   `<workdir>/processed/holding.csv` sentinel says we already
 *   extracted.
 * - `out_report_json` (out, owned): JSON object with extract
 *   stats. Caller frees via `kglite_free_string`.
 * - `out_error_msg` (out, owned, may be null).
 *
 * # Safety
 *
 * `workdir_path` and (if non-null) `slice_json` must be
 * null-terminated UTF-8.
 */

KgliteStatusCode kglite_datasets_sec_run_all(const char *workdir_path,
                                             const char *slice_json,
                                             uint8_t force,
                                             const char **out_report_json,
                                             const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_WIKIDATA)
/**
 * Ensure the Wikidata `latest-truthy.nt.bz2` dump is present
 * under `<workdir>/cache/`. Resumable: a partially-downloaded
 * file gets continued via HTTP `Range` requests. Cooldown:
 * fully-present files within `cooldown_days` of their mtime
 * skip the re-fetch entirely.
 *
 * # Arguments
 *
 * - `workdir_path` (in, borrowed): root for the workdir layout.
 * - `cooldown_days` (in): how stale the cached dump can be before
 *   re-fetching. Wheel default: 7.
 * - `verbose` (in): non-zero turns on the engine's progress
 *   logging.
 * - `out_dump_path` (out, owned): JSON-encoded path string to the
 *   downloaded dump (e.g. `"\"<workdir>/cache/latest-truthy.nt.bz2\""`).
 *   We JSON-encode the path because filesystem paths can contain
 *   characters that need escaping in some downstream consumers.
 * - `out_remote_mtime_iso` (out, owned, may be null): if the
 *   server returned a `Last-Modified` header, the ISO 8601
 *   timestamp string. Set to null if the probe failed.
 * - `out_error_msg` (out, owned, may be null).
 *
 * # Safety
 *
 * `workdir_path` must be null-terminated UTF-8. The out-pointers
 * must be valid writable slots.
 */

KgliteStatusCode kglite_datasets_wikidata_ensure_dump(const char *workdir_path,
                                                      int64_t cooldown_days,
                                                      uint8_t verbose,
                                                      const char **out_dump_path,
                                                      const char **out_remote_mtime_iso,
                                                      const char **out_error_msg);
#endif

#if defined(KGLITE_FEATURE_WIKIDATA)
/**
 * Sync HEAD-request probe for the dump's `Last-Modified` header.
 * Returns the timestamp as RFC 3339 / ISO 8601 string, or null if
 * the probe failed (network down, server returned no header,
 * etc.).
 *
 * # Arguments
 *
 * - `out_iso` (out, owned, may be null): ISO 8601 string on
 *   success; null on probe failure. Caller frees via
 *   [`kglite_free_string`](crate::kglite_free_string).
 *
 * # Safety
 *
 * `out_iso` must be a valid writable pointer.
 */
 KgliteStatusCode kglite_datasets_wikidata_remote_last_modified(const char **out_iso);
#endif

#if defined(KGLITE_FEATURE_WIKIDATA)
/**
 * Run the cache-freshness decision tree. Pure-CPU; the only I/O
 * is the file-mtime stat that the engine's `decide()` performs
 * internally on the two meta-paths. Returns a JSON object:
 *
 * ```json
 * {"decision": "build" | "load" | "rebuild", "reason": "..." }
 * ```
 *
 * # Arguments
 *
 * - `force_rebuild` (in): non-zero → always `Build("force_rebuild")`.
 * - `graph_meta_path` (in, borrowed): path to
 *   `<graph_dir>/disk_graph_meta.json`. Missing file →
 *   `Build("no_cache")`.
 * - `source_meta_path` (in, borrowed): path to
 *   `<graph_dir>/wikidata_source.json`. May be missing on graphs
 *   built before source-meta stamping landed.
 * - `cooldown_days` (in): graphs younger than this skip the
 *   remote probe.
 * - `remote_mtime_iso` (in, borrowed, may be null): RFC 3339 /
 *   ISO 8601 timestamp from a prior call to
 *   [`kglite_datasets_wikidata_remote_last_modified`]. Null →
 *   probe was skipped or failed.
 * - `out_decision_json` (out, owned).
 * - `out_error_msg` (out, owned, may be null).
 *
 * # Safety
 *
 * All input strings must be null-terminated UTF-8.
 * `out_decision_json` must be a valid writable pointer.
 */

KgliteStatusCode kglite_datasets_wikidata_decide_cache(uint8_t force_rebuild,
                                                       const char *graph_meta_path,
                                                       const char *source_meta_path,
                                                       int64_t cooldown_days,
                                                       const char *remote_mtime_iso,
                                                       const char **out_decision_json,
                                                       const char **out_error_msg);
#endif

/**
 * Free an embedder handle. Idempotent on null.
 *
 * # Safety
 *
 * `embedder` must be either null or a valid pointer previously
 * returned by a `kglite_embedder_*_new` factory and not yet
 * freed. Calling twice on the same pointer is UB.
 *
 * **Do NOT free** an embedder that has been handed to
 * [`kglite_session_set_embedder`] — the session retains a clone
 * of the inner Arc; you may free your handle after the call to
 * set_embedder (the Arc keeps the embedder alive until the
 * session drops). For symmetry with other handles, the safest
 * pattern is: factory → set_embedder → free_embedder. Once the
 * Arc is shared, the original handle is no longer special.
 */
 void kglite_embedder_free(struct KgliteEmbedder *embedder);

/**
 * Attach an embedder to a session. The session retains a clone
 * of the embedder's inner `Arc`, so subsequent
 * [`kglite_session_execute_read`](crate::kglite_session_execute_read)
 * calls have access to `text_score()` and other embedder-backed
 * Cypher functions.
 *
 * The caller may free the embedder handle after this call
 * returns — the `Arc` clone keeps the underlying embedder
 * alive for the session's lifetime.
 *
 * # Safety
 *
 * `session` and `embedder` must be valid handles previously
 * returned by `kglite_session_new` and a `kglite_embedder_*_new`
 * factory respectively, neither yet freed.
 */

KgliteStatusCode kglite_session_set_embedder(struct KgliteSession *session,
                                             const struct KgliteEmbedder *embedder);

#if defined(KGLITE_FEATURE_FASTEMBED)
/**
 * Construct a fastembed-rs-backed embedder.
 *
 * fastembed-rs downloads ONNX model weights on first
 * `embed()` call (cached at `~/.cache/fastembed/`). The factory
 * does NOT block on download — model name validation only. The
 * first Cypher query using `text_score()` triggers the download.
 *
 * # Arguments
 *
 * - `model_name` (in, borrowed): a known fastembed model name,
 *   e.g. `"BAAI/bge-m3"`, `"sentence-transformers/all-MiniLM-L6-v2"`.
 *   See fastembed-rs's TextEmbedding::list_supported_models() for
 *   the full list.
 * - `out_embedder` (out, owned): on success, set to an embedder
 *   handle. Caller must free via [`kglite_embedder_free`] (or
 *   transfer ownership via [`kglite_session_set_embedder`]).
 * - `out_error_msg` (out, owned, may be null): on failure, set to
 *   an owned error string.
 *
 * # Errors
 *
 * - `KGLITE_STATUS_CODE_NULL_POINTER` — required pointer is null
 * - `KGLITE_STATUS_CODE_INVALID_UTF8` — `model_name` isn't valid UTF-8
 * - `KGLITE_STATUS_CODE_INVALID_ARGUMENT` — `model_name` isn't a known
 *   fastembed model
 *
 * # Feature gate
 *
 * Available only when `kglite-c` is built with the `fastembed`
 * Cargo feature.
 *
 * # Safety
 *
 * `model_name` must be a null-terminated UTF-8 string.
 * `out_embedder` must be a valid writable pointer.
 */

KgliteStatusCode kglite_embedder_fastembed_new(const char *model_name,
                                               struct KgliteEmbedder **out_embedder,
                                               const char **out_error_msg);
#endif

/**
 * Create a new, empty in-memory knowledge graph.
 *
 * The returned handle owns a fresh, empty `DirGraph` — the C-side
 * analogue of constructing `KnowledgeGraph()` in Python. Build it up
 * by opening a session ([`kglite_session_new`](crate::kglite_session_new))
 * and running `CREATE` / `MERGE` Cypher through
 * [`kglite_session_execute_mut`](crate::kglite_session_execute_mut), or
 * by bulk-loading via the dataset / blueprint entry points. Before this
 * existed, the only way to obtain a graph at the C boundary was to load
 * a pre-built `.kgl` file — a binding could not start one from scratch.
 *
 * # Returns
 *
 * A non-null `KgliteGraph*` the caller must free with
 * [`kglite_graph_free`], or hand to
 * [`kglite_session_new`](crate::kglite_session_new) which takes
 * ownership. Returns null only on allocation failure.
 */
 struct KgliteGraph *kglite_graph_new(void);

/**
 * Create a fresh, empty knowledge graph in an explicit storage mode.
 *
 * `mode` is `"memory"` (alias `"default"`), `"mapped"`, or `"disk"` — the
 * same mode vocabulary as Python's `storage=` argument:
 *
 * - `"memory"` — heap-resident (the default; same as [`kglite_graph_new`]).
 * - `"mapped"` — property columns spill to mmap during build, so a graph
 *   larger than RAM can be constructed; saves to a `.kgl` file.
 * - `"disk"` — CSR + mmap on-disk directory format for very large graphs;
 *   **requires** `path` (the directory that becomes the graph).
 *
 * This is the create/ingest entry point. Opening an existing graph
 * ([`kglite_load_file`]) auto-detects its mode, so no mode argument is
 * needed there.
 *
 * # Arguments
 *
 * - `mode` (in, borrowed): UTF-8 mode string, null-terminated.
 * - `path` (in, borrowed): UTF-8 directory path for `"disk"`, else null.
 * - `out_graph` (out, owned): set to the new graph handle on success
 *   (free via [`kglite_graph_free`], or hand to
 *   [`kglite_session_new`](crate::kglite_session_new)); null on failure.
 * - `out_error_msg` (out, owned): owned error message on failure (free via
 *   [`kglite_free_string`](crate::kglite_free_string)); null on success.
 *
 * # Errors
 *
 * - `KGLITE_ERR_NULL_POINTER` — `mode` or `out_graph` is null
 * - `KGLITE_ERR_INVALID_UTF8` — `mode` / `path` isn't valid UTF-8
 * - `KGLITE_ERR_INVALID_ARGUMENT` — unknown mode, or `"disk"` with no path
 * - `KGLITE_ERR_FILE_IO` — failed to create the disk-graph directory
 *
 * # Safety
 *
 * `mode` must be a null-terminated UTF-8 string; `path` null or the same;
 * `out_graph` a valid `*mut KgliteGraph` slot; `out_error_msg` null or a
 * valid slot.
 */

KgliteStatusCode kglite_graph_new_in_mode(const char *mode,
                                          const char *path,
                                          struct KgliteGraph **out_graph,
                                          const char **out_error_msg);

/**
 * Load a knowledge graph from disk. Accepts `.kgl` files
 * (single-file mmap format) and directories (disk-backed CSR
 * layout) — the loader picks the right path based on what's at
 * `path`.
 *
 * # Arguments
 *
 * - `path` (in, borrowed): UTF-8 file path, null-terminated.
 * - `out_graph` (out, owned): set to the loaded graph handle on
 *   success; caller must free via [`kglite_graph_free`]. Set to
 *   null on failure.
 * - `out_error_msg` (out, owned): set to an owned error message
 *   on failure; caller must free via
 *   [`kglite_free_string`](crate::kglite_free_string). Set to
 *   null on success.
 *
 * # Errors
 *
 * - `KGLITE_ERR_NULL_POINTER` — `path` or `out_graph` is null
 * - `KGLITE_ERR_INVALID_UTF8` — `path` isn't valid UTF-8
 * - `KGLITE_ERR_FILE_NOT_FOUND` — `path` doesn't exist
 * - `KGLITE_ERR_FILE_FORMAT` — file isn't a valid `.kgl` /
 *   disk-graph directory
 * - `KGLITE_ERR_FILE_IO` — I/O failure during read
 *
 * # Safety
 *
 * `path` must point to a null-terminated UTF-8 string.
 * `out_graph` must be a valid writable pointer to a
 * `*mut KgliteGraph` slot. `out_error_msg` may be null (the
 * caller doesn't care about the message); otherwise it must
 * point to a valid writable `*const c_char` slot.
 */

KgliteStatusCode kglite_load_file(const char *path,
                                  struct KgliteGraph **out_graph,
                                  const char **out_error_msg);

#if defined(KGLITE_FEATURE_RDF)
/**
 * Load an RDF file into a fresh in-memory graph — the C-side handle on
 * the wheel's `kglite.load_rdf`. Dispatches on the extension: `.ttl`
 * (Turtle), `.nt` (N-Triples), `.nq` (N-Quads), `.trig` (TriG).
 *
 * The RDF → property-graph fold: object literals become typed node
 * properties, resource objects become edges, and `rdf:type` sets the
 * node label (first wins; extras kept in an `rdf_types` property).
 * Predicate / type IRIs are CURIE-compacted with a `__` separator
 * (so `[:foaf__knows]` matches in Cypher); each node keeps its full
 * subject IRI in a `uri` property. In-memory backend only.
 *
 * # Arguments
 *
 * - `path` (in, borrowed): UTF-8 file path; the extension picks the parser.
 * - `languages_json` (in, borrowed): JSON array of language tags to keep
 *   (e.g. `["en","de"]`), or null to keep all literals.
 * - `label_predicates_json` (in, borrowed): JSON array of predicate IRIs
 *   whose literal object sets the node title, or null for
 *   `["http://www.w3.org/2000/01/rdf-schema#label"]`.
 * - `keep_full_iris` (in): non-zero keeps full IRIs instead of CURIEs.
 * - `default_type` (in, borrowed): node type for subjects without an
 *   `rdf:type`, or null for `"Resource"`.
 * - `max_triples` (in): stop after this many triples; negative = no limit.
 * - `out_graph` (out, owned): the loaded graph on success (free via
 *   [`kglite_graph_free`] or hand to
 *   [`kglite_session_new`](crate::kglite_session_new)); null on failure.
 * - `out_stats_json` (out, owned): `{"nodes":N,"edges":M,"triples":T}` on
 *   success — free via [`kglite_free_string`](crate::kglite_free_string).
 *   May be null if the caller doesn't want stats.
 * - `out_error_msg` (out, owned): error message on failure — free via
 *   [`kglite_free_string`](crate::kglite_free_string); null on success.
 *
 * # Errors
 *
 * - `KGLITE_ERR_NULL_POINTER` — `path` or `out_graph` is null
 * - `KGLITE_ERR_INVALID_UTF8` — a string argument isn't valid UTF-8
 * - `KGLITE_ERR_INVALID_ARGUMENT` — a `*_json` arg isn't a JSON string
 *   array, or the file extension isn't a supported RDF format
 * - `KGLITE_ERR_FILE_NOT_FOUND` — `path` doesn't exist
 * - `KGLITE_ERR_FILE_FORMAT` — a parse error in the RDF
 *
 * # Safety
 *
 * String arguments must each be a null-terminated UTF-8 string or null;
 * `out_graph` a valid writable `*mut KgliteGraph` slot; `out_stats_json`
 * and `out_error_msg` null or valid writable slots.
 */

KgliteStatusCode kglite_load_rdf(const char *path,
                                 const char *languages_json,
                                 const char *label_predicates_json,
                                 uint8_t keep_full_iris,
                                 const char *default_type,
                                 int64_t max_triples,
                                 struct KgliteGraph **out_graph,
                                 const char **out_stats_json,
                                 const char **out_error_msg);
#endif

/**
 * Save a knowledge graph to disk. The on-disk format depends on
 * the underlying storage mode — in-memory and mapped graphs
 * produce a `.kgl` single-file; disk-backed graphs produce / fill
 * a directory.
 *
 * The write is atomic (temp + rename) and **durable** (file +
 * parent-directory fsync) — a crash mid-save can't tear the file.
 * Use [`kglite_save_graph_durable`] with `fsync == 0` for the fast,
 * non-durable opt-out.
 *
 * # Arguments
 *
 * - `graph` (in, borrowed): the graph to save.
 * - `path` (in, borrowed): UTF-8 destination path,
 *   null-terminated.
 * - `out_error_msg` (out, owned): set to an owned error message
 *   on failure; caller must free via
 *   [`kglite_free_string`](crate::kglite_free_string). Set to
 *   null on success.
 *
 * # Errors
 *
 * - `KGLITE_ERR_NULL_POINTER` — `graph` or `path` is null
 * - `KGLITE_ERR_INVALID_UTF8` — `path` isn't valid UTF-8
 * - `KGLITE_ERR_FILE_IO` — write failed
 *
 * # Safety
 *
 * `graph` must be a valid `*mut KgliteGraph` previously returned
 * by a `kglite_*` function and not yet freed. `path` must be a
 * null-terminated UTF-8 string.
 */

KgliteStatusCode kglite_save_graph(struct KgliteGraph *graph,
                                   const char *path,
                                   const char **out_error_msg);

/**
 * Free a graph handle. Idempotent on null (no-op).
 *
 * # Safety
 *
 * `graph` must be either null or a pointer previously returned by
 * [`kglite_load_file`] (or any future `kglite_*` function that
 * returns a `*mut KgliteGraph`) and not yet freed. Calling twice
 * on the same pointer is UB.
 *
 * **Do NOT free** a graph handle that has been handed to
 * [`kglite_session_new`](crate::kglite_session_new) — the session
 * takes ownership and frees on its own teardown.
 */
 void kglite_graph_free(struct KgliteGraph *graph);

/**
 * Generate a synthetic benchmark/demo graph as CSVs + a manifest under
 * `out_dir`, in bounded memory. Load the result with [`kglite_load_file`]
 * pointed at `out_dir` — the C-side handle on `kglite.graphgen(...)`, the
 * "hello, query a graph" data source for a fresh binding.
 *
 * `zipf` != 0 uses a Zipf degree distribution (high-degree hubs) with
 * exponent `zipf_exp`; `zipf` == 0 uses uniform degree.
 *
 * On success `out_stats_json` is set to an owned `{"nodes": N, "edges": M}`
 * string — free via [`kglite_free_string`](crate::kglite_free_string).
 *
 * # Safety
 *
 * `out_dir` must be a null-terminated UTF-8 path; `out_stats_json` a valid
 * writable `*const c_char` slot; `out_error_msg` null or a valid slot.
 */

KgliteStatusCode kglite_graphgen_to_dir(uint64_t persons,
                                        uint64_t knows_per,
                                        uint64_t seed,
                                        uint8_t zipf,
                                        double zipf_exp,
                                        const char *out_dir,
                                        const char **out_stats_json,
                                        const char **out_error_msg);

/**
 * Build a graph declaratively from a blueprint file + a directory of
 * CSVs — the C-side handle on the wheel's `from_blueprint`. Loads the
 * JSON/YAML blueprint at `blueprint_path`, builds into a fresh graph
 * reading CSVs relative to `csv_dir`, and returns the populated graph.
 *
 * On success `out_graph` is set to a `KgliteGraph*` (free via
 * [`kglite_graph_free`] or hand to [`kglite_session_new`](crate::kglite_session_new)),
 * and `out_report_json` to an owned
 * `{"nodes_by_type":{..},"edges_by_type":{..},"warnings":[..],"errors":[..],"provisional_purged":N}`
 * string — free via [`kglite_free_string`](crate::kglite_free_string).
 *
 * # Safety
 *
 * `blueprint_path` / `csv_dir` must be null-terminated UTF-8 paths;
 * `out_graph` / `out_report_json` valid writable slots; `out_error_msg`
 * null or a valid slot.
 */

KgliteStatusCode kglite_blueprint_build(const char *blueprint_path,
                                        const char *csv_dir,
                                        struct KgliteGraph **out_graph,
                                        const char **out_report_json,
                                        const char **out_error_msg);

/**
 * Save a graph to a `.kgl` file with an explicit durability choice.
 *
 * `fsync` != 0 is exactly [`kglite_save_graph`]: mode-aware (disk dir vs
 * in-memory `.kgl`), atomic temp+rename, and the file + parent directory
 * are flushed to stable storage before returning — durable across power
 * loss, at the cost of fsync latency.
 *
 * `fsync` == 0 is the fast, **non-durable** opt-out: same mode-aware
 * atomic rename (never a torn file) but the fsync barrier is skipped, so
 * the bytes may not survive an OS/power crash. Use it only for bulk or
 * throwaway saves where you'll re-save or can rebuild.
 *
 * # Safety
 *
 * `graph` must be a valid handle; `path` a null-terminated UTF-8 path;
 * `out_error_msg` null or a valid slot.
 */

KgliteStatusCode kglite_save_graph_durable(struct KgliteGraph *graph,
                                           const char *path,
                                           uint8_t fsync,
                                           const char **out_error_msg);

/**
 * Serialize a graph to an in-memory `.kgl` byte buffer (no file). On
 * success `*out_buf` / `*out_len` describe an owned buffer the caller
 * MUST free with [`kglite_free_bytes`]. Pair with
 * [`kglite_graph_from_bytes`] to round-trip a graph through bytes (IPC,
 * object storage, …).
 *
 * # Safety
 *
 * `graph` valid; `out_buf` a valid `*mut u8` slot; `out_len` a valid
 * `usize` slot; `out_error_msg` null or valid.
 */

KgliteStatusCode kglite_graph_to_bytes(struct KgliteGraph *graph,
                                       uint8_t **out_buf,
                                       uintptr_t *out_len,
                                       const char **out_error_msg);

/**
 * Free a byte buffer returned by [`kglite_graph_to_bytes`]. Pass the
 * same `buf` / `len` pair. Null `buf` is a no-op.
 *
 * # Safety
 *
 * `buf` / `len` must be a pair previously returned by
 * [`kglite_graph_to_bytes`] and not yet freed.
 */
 void kglite_free_bytes(uint8_t *buf, uintptr_t len);

/**
 * Load a graph from an in-memory `.kgl` byte buffer — the inverse of
 * [`kglite_graph_to_bytes`].
 *
 * # Safety
 *
 * `data` / `len` must describe a readable buffer; `out_graph` a valid
 * writable slot; `out_error_msg` null or a valid slot.
 */

KgliteStatusCode kglite_graph_from_bytes(const uint8_t *data,
                                         uintptr_t len,
                                         struct KgliteGraph **out_graph,
                                         const char **out_error_msg);

/**
 * Compute a JSON schema overview of a graph: node types (count +
 * property types), connection types (endpoints + property names),
 * indexes, and total node/edge counts. The C-side handle on the
 * agent-facing schema — call it right after load / build / from_bytes
 * to learn a graph's shape before querying.
 *
 * On success `out_json` is set to an owned JSON object — free via
 * [`kglite_free_string`](crate::kglite_free_string). Operates on a graph
 * handle (before it is moved into a session).
 *
 * # Safety
 *
 * `graph` must be a valid handle; `out_json` a valid writable slot;
 * `out_error_msg` null or a valid slot.
 */

KgliteStatusCode kglite_compute_schema_json(struct KgliteGraph *graph,
                                            const char **out_json,
                                            const char **out_error_msg);

/**
 * Return the column names as a JSON array string:
 * `["col1", "col2", ...]`.
 *
 * The returned string is OWNED by the caller and must be freed
 * via [`kglite_free_string`](crate::kglite_free_string). Returns
 * null on serialization failure (shouldn't happen — column names
 * are always serializable).
 */
 const char *kglite_cypher_result_columns_json(const struct KgliteCypherResult *result);

/**
 * Return all rows as a JSON array of objects keyed by column
 * name: `[{"col1": v1, "col2": v2}, ...]`.
 *
 * Cell values are **natural** JSON (`2`, `"x"`, `[..]`, `{..}`) via
 * [`kglite_value_to_json`](kglite::api::param::kglite_value_to_json) —
 * not serde's externally-tagged enum encoding — so a binding parses
 * `{"n": 2}`, not `{"n": {"Int64": 2}}`.
 *
 * For large result sets this materializes the entire JSON blob
 * in memory. Future v2 will add pull-row-by-row accessors; for
 * now this is fine for the common-case query sizes.
 *
 * The returned string is OWNED by the caller and must be freed
 * via [`kglite_free_string`](crate::kglite_free_string). Returns
 * null on serialization failure.
 */
 const char *kglite_cypher_result_rows_json(const struct KgliteCypherResult *result);

/**
 * Return the number of rows in the result. Useful for callers
 * that want to size buffers before requesting the JSON blob.
 */
 uintptr_t kglite_cypher_result_row_count(const struct KgliteCypherResult *result);

/**
 * Free a result handle. Idempotent on null (no-op).
 *
 * # Safety
 *
 * `result` must be either null or a valid pointer previously
 * returned by [`kglite_session_execute_read`](crate::kglite_session_execute_read)
 * or [`kglite_session_execute_mut`](crate::kglite_session_execute_mut)
 * and not yet freed.
 */
 void kglite_cypher_result_free(struct KgliteCypherResult *result);

/**
 * Create a new session from a graph handle. The session takes
 * ownership of the graph — the caller MUST NOT call
 * [`kglite_graph_free`](crate::kglite_graph_free) on the handle
 * after this call. Free the session via
 * [`kglite_session_free`] when done.
 *
 * # Arguments
 *
 * - `graph` (in, MOVED): graph handle. After this call, the
 *   pointer is no longer valid for any other use.
 * - `out_session` (out, owned): set to the session handle on
 *   success; caller must free via [`kglite_session_free`].
 *
 * # Errors
 *
 * - `KGLITE_ERR_NULL_POINTER` — `graph` or `out_session` is null
 *
 * # Safety
 *
 * `graph` must be a valid `*mut KgliteGraph` previously returned
 * by [`kglite_load_file`](crate::kglite_load_file) and not yet
 * freed or moved into another session. `out_session` must be a
 * valid writable pointer to a `*mut KgliteSession` slot.
 */
 KgliteStatusCode kglite_session_new(struct KgliteGraph *graph, struct KgliteSession **out_session);

/**
 * Run a read-only Cypher query.
 *
 * # Arguments
 *
 * - `session` (in, borrowed): the session.
 * - `query` (in, borrowed): UTF-8 Cypher query, null-terminated.
 * - `params_json` (in, borrowed, may be null): JSON object of
 *   parameter bindings. Pass null or `"{}"` for no params.
 * - `out_result` (out, owned): on success, set to the result
 *   handle; caller must free via [`kglite_cypher_result_free`].
 * - `out_error_msg` (out, owned, may be null): on failure, set
 *   to the error message; caller must free via
 *   [`kglite_free_string`](crate::kglite_free_string).
 *
 * # Errors
 *
 * Any `KgErrorCode` variant — Cypher syntax / type mismatch /
 * timeout / execution error / node-not-found / argument
 * validation. The error message describes the specific failure.
 *
 * # Safety
 *
 * `session` must be valid. `query` and (if non-null) `params_json`
 * must be null-terminated UTF-8 strings.
 */

KgliteStatusCode kglite_session_execute_read(const struct KgliteSession *session,
                                             const char *query,
                                             const char *params_json,
                                             struct KgliteCypherResult **out_result,
                                             const char **out_error_msg);

/**
 * Run a read-only Cypher query with execution options. Same as
 * [`kglite_session_execute_read`], plus:
 *
 * - `timeout_ms`: past this wall-clock budget the query returns
 *   `CypherTimeout`. `0` = no deadline.
 * - `max_rows`: reject the query (error) if it would produce more than
 *   this many rows — a safety guard against runaway results, not a
 *   silent truncation; add a `LIMIT` clause to bound output. `0` = no
 *   limit.
 *
 * # Safety
 *
 * Same as [`kglite_session_execute_read`].
 */

KgliteStatusCode kglite_session_execute_read_opts(const struct KgliteSession *session,
                                                  const char *query,
                                                  const char *params_json,
                                                  uint64_t timeout_ms,
                                                  uint64_t max_rows,
                                                  struct KgliteCypherResult **out_result,
                                                  const char **out_error_msg);

/**
 * Run a mutating Cypher query. Same shape as
 * [`kglite_session_execute_read`] but accepts CREATE / SET /
 * DELETE / REMOVE / MERGE statements. The session's underlying
 * graph is auto-committed after a successful execute (no
 * explicit begin/commit in v1 — explicit transactions land in
 * a future ABI version once a binding needs them).
 *
 * # Safety
 *
 * Same as [`kglite_session_execute_read`] except `session` is
 * declared as `*mut` (the call mutates the session's interior
 * graph via commit-swap).
 */

KgliteStatusCode kglite_session_execute_mut(struct KgliteSession *session,
                                            const char *query,
                                            const char *params_json,
                                            struct KgliteCypherResult **out_result,
                                            const char **out_error_msg);

/**
 * Run several read-only Cypher queries against a single consistent
 * snapshot, in one lock acquisition.
 *
 * `queries_json` is a JSON array of objects, each `{"query": "...",
 * "params": {...}}` (the `params` key is optional). Every query sees
 * the same snapshot, taken once up front — cheaper and more consistent
 * than N separate [`kglite_session_execute_read`] calls when a binding
 * issues many small reads.
 *
 * On success `out_results_json` is set to an owned JSON string: an
 * array of `{"columns": [...], "rows": [{...}]}` objects, one per input
 * query in order, with the same natural-value encoding as
 * [`kglite_cypher_result_rows_json`]. Free it with
 * [`kglite_free_string`](crate::kglite_free_string).
 *
 * The batch aborts on the first failing query: `out_results_json` is
 * set to null and the status code / `out_error_msg` describe that
 * query's failure.
 *
 * # Safety
 *
 * `session` must be valid; `queries_json` a null-terminated UTF-8 JSON
 * array; `out_results_json` a valid writable `*const c_char` slot;
 * `out_error_msg` null or a valid writable slot.
 */

KgliteStatusCode kglite_session_execute_read_batch(const struct KgliteSession *session,
                                                   const char *queries_json,
                                                   const char **out_results_json,
                                                   const char **out_error_msg);

/**
 * Run several mutating Cypher queries in a single transaction — one
 * `begin`, N executes (each sees the previous query's writes), a single
 * `commit`. The batch is **atomic**: if any query fails, the
 * transaction is dropped uncommitted and none of the batch's mutations
 * reach the graph.
 *
 * `queries_json` / `out_results_json` have the same shape as
 * [`kglite_session_execute_read_batch`]. On failure `out_results_json`
 * is null and the status / `out_error_msg` describe the failing query.
 *
 * # Safety
 *
 * Same as [`kglite_session_execute_read_batch`] except `session` is
 * `*mut` (the call mutates the session's interior graph via
 * commit-swap).
 */

KgliteStatusCode kglite_session_execute_mut_batch(struct KgliteSession *session,
                                                  const char *queries_json,
                                                  const char **out_results_json,
                                                  const char **out_error_msg);

/**
 * Bulk-create edges addressed by **stable node id + type**, bypassing
 * Cypher — the fast ingest path for bindings loading many edges.
 *
 * `edges_json` is a JSON array of objects:
 * `{"src_id": <id>, "src_type": "Person", "dst_id": <id>,
 *   "dst_type": "Company", "type": "WORKS_AT", "props": {...}}`
 * (`props` optional). `src_id`/`dst_id` are the nodes' stable ids (the
 * same value `n.id` returns), not internal indices. Runs in one
 * transaction: the whole batch commits together, or — on error — none
 * of it lands. Endpoints must already exist; an edge whose source or
 * target id isn't found for its declared type is skipped and counted.
 *
 * On success `out_report_json` is set to an owned JSON object
 * `{"connections_created": N, "skipped_missing_endpoint": M}`; free it
 * with [`kglite_free_string`](crate::kglite_free_string).
 *
 * This wraps the shared core primitive
 * [`add_edges_from_specs`](kglite::api::mutation::add_edges_from_specs) —
 * the same engine the Python `add_connections` DataFrame path uses.
 *
 * # Safety
 *
 * `session` must be valid; `edges_json` a null-terminated UTF-8 JSON
 * array; `out_report_json` a valid writable `*const c_char` slot;
 * `out_error_msg` null or a valid writable slot.
 */

KgliteStatusCode kglite_create_edges_batch(struct KgliteSession *session,
                                           const char *edges_json,
                                           const char **out_report_json,
                                           const char **out_error_msg);

/**
 * Free a session handle. Idempotent on null (no-op).
 *
 * # Safety
 *
 * `session` must be either null or a valid pointer previously
 * returned by [`kglite_session_new`] and not yet freed.
 */
 void kglite_session_free(struct KgliteSession *session);

/**
 * Return the canonical human-readable name of a status code (e.g.
 * `"CypherSyntax"`, `"NodeNotFound"`, `"InvalidUtf8"`).
 *
 * The returned string is OWNED by the caller and must be freed
 * via [`kglite_free_string`](crate::kglite_free_string). Returns
 * null on `Ok` (no error to name).
 */
 const char *kglite_status_code_name(KgliteStatusCode code);

/**
 * Return the Neo4j wire status code for a status code (e.g.
 * `"Neo.ClientError.Statement.SyntaxError"`). Useful for bindings
 * implementing the Neo4j Bolt wire protocol or compatible HTTP
 * APIs.
 *
 * The returned string is OWNED by the caller and must be freed
 * via [`kglite_free_string`](crate::kglite_free_string). Returns
 * null on `Ok` or on C-ABI-only error codes that have no Neo4j
 * counterpart (`InvalidUtf8`, `NullPointer`).
 */
 const char *kglite_status_code_neo4j_status(KgliteStatusCode code);

/**
 * Return the HTTP status code mapping for a status code (e.g.
 * 400 for `CypherSyntax`, 404 for `NodeNotFound`, 500 for
 * `Internal`). Useful for REST/gRPC bindings.
 *
 * Returns 0 for `Ok` and 500 for C-ABI-only codes (`InvalidUtf8`
 * = 400 / bad request from caller, `NullPointer` = 400).
 */
 uint16_t kglite_status_code_http_status(KgliteStatusCode code);

/**
 * Free a string previously returned by any `kglite_*` function.
 *
 * Safety: `s` must be either null or a pointer previously returned
 * by a `kglite_*` function (these all flow through
 * [`alloc_c_string`]). Calling twice on the same pointer is UB.
 * Calling with a pointer to a string allocated by the C caller's
 * own `malloc` is UB.
 *
 * Passing null is safe (treated as a no-op).
 *
 * # Examples
 *
 * ```c
 * const char* col_json = kglite_cypher_result_columns_json(result);
 * printf("%s\n", col_json);
 * kglite_free_string(col_json);
 * ```
 */
 void kglite_free_string(const char *s);

#endif  /* KGLITE_H_INCLUDED */
