#ifndef CF_STEP_ABI_H
#define CF_STEP_ABI_H

#include <stddef.h>
#include <stdint.h>

#ifdef _WIN32
  #ifdef CF_STEP_ABI_EXPORTS
    #define CF_EXPORT __declspec(dllexport)
  #else
    #define CF_EXPORT __declspec(dllimport)
  #endif
#else
  #define CF_EXPORT __attribute__((visibility("default")))
#endif

#ifdef __cplusplus
extern "C" {
#endif

typedef enum CfStatusCode {
  CF_STATUS_OK = 0,
  CF_STATUS_ERROR = 1,
  CF_STATUS_INVALID = 2,
  CF_STATUS_NOT_IMPLEMENTED = 3
} CfStatusCode;

typedef enum CfStorageType {
  CF_STORAGE_OPAQUE = 0,
  CF_STORAGE_BYTES = 1,
  CF_STORAGE_BOOL = 2,
  CF_STORAGE_I64 = 3,
  CF_STORAGE_F64 = 4,
  CF_STORAGE_STRING = 5,
  CF_STORAGE_JSON = 6,
  CF_STORAGE_VECTOR_F64 = 7
} CfStorageType;

typedef struct CfValueHandle {
  CfStorageType storage_type;
  void* data;
  size_t size;
} CfValueHandle;

typedef void* (*CfAllocFn)(size_t size, void* user_data);
typedef void (*CfFreeFn)(void* ptr, void* user_data);

typedef struct CfAllocator {
  CfAllocFn alloc;
  CfFreeFn free;
  void* user_data;
} CfAllocator;

typedef struct CfCallContext {
  CfAllocator* allocator;
  void* user_data;
} CfCallContext;

/* Optional runtime-provided execution hints for a single step invocation.
   If present, ctx->user_data points to a CfRuntimeHintsV1 instance. */
typedef enum CfRuntimeHintFlags {
  CF_RUNTIME_HINT_DETERMINISTIC = 1u << 0
} CfRuntimeHintFlags;

typedef struct CfRuntimeHintsV1 {
  uint32_t version;       /* Must be 1 for this struct. */
  uint32_t flags;         /* CfRuntimeHintFlags bitmask. */
  uint32_t thread_budget; /* Max threads permitted for step-internal parallelism. */
  uint32_t reserved;
} CfRuntimeHintsV1;

static inline const CfRuntimeHintsV1* cf_runtime_hints_v1(const CfCallContext* ctx) {
  if (!ctx || !ctx->user_data) return NULL;
  const CfRuntimeHintsV1* hints = (const CfRuntimeHintsV1*)ctx->user_data;
  return (hints->version == 1u) ? hints : NULL;
}

static inline uint32_t cf_runtime_thread_budget(const CfCallContext* ctx, uint32_t fallback_value) {
  const CfRuntimeHintsV1* hints = cf_runtime_hints_v1(ctx);
  return hints ? hints->thread_budget : fallback_value;
}

static inline int cf_runtime_is_deterministic(const CfCallContext* ctx) {
  const CfRuntimeHintsV1* hints = cf_runtime_hints_v1(ctx);
  return hints ? ((hints->flags & CF_RUNTIME_HINT_DETERMINISTIC) != 0) : 0;
}

typedef enum CfValidationSeverity {
  CF_VALIDATION_INFO = 0,
  CF_VALIDATION_WARNING = 1,
  CF_VALIDATION_ERROR = 2
} CfValidationSeverity;

typedef struct CfValidationIssue {
  CfValidationSeverity severity;
  const char* message;
  const char* parameter_key;
  const char* port_key;
} CfValidationIssue;

typedef struct CfValidationReport {
  CfValidationIssue* issues;
  size_t issue_count;
  size_t issue_capacity;
  /* Allocator is typically arena-style; issues/strings are not freed individually.
     On grow, the old issues buffer is freed only if allocator->free is provided. */
  CfAllocator* allocator;
} CfValidationReport;

typedef CfStatusCode (*CfStepCallFn)(
  CfCallContext* ctx,
  const CfValueHandle* params,
  size_t n_params,
  const CfValueHandle* inputs,
  size_t n_inputs,
  CfValueHandle* outputs,
  size_t n_outputs
);

typedef CfStatusCode (*CfStepValidateFn)(
  CfCallContext* ctx,
  const CfValueHandle* params,
  size_t n_params,
  const CfValueHandle* inputs,
  size_t n_inputs,
  CfValidationReport* out_report
);

typedef struct CfStepVTable {
  const char* step_iri;
  const char* signature_hash;
  CfStepCallFn call;
  CfStepValidateFn validate;
} CfStepVTable;

CF_EXPORT size_t cf_list_steps(const char*** out_step_iris);
CF_EXPORT int cf_resolve_step(const char* step_iri, CfStepVTable* out);

#ifdef __cplusplus
}
#endif

#include <string.h>

#ifdef __cplusplus
extern "C" {
#endif

static inline void* cf_alloc(CfAllocator* allocator, size_t size) {
  if (!allocator || !allocator->alloc) return NULL;
  return allocator->alloc(size, allocator->user_data);
}

static inline CfStatusCode cf_out_bytes(CfCallContext* ctx, CfValueHandle* out, CfStorageType storage_type,
                                        const void* data, size_t size) {
  if (!out || !ctx || !ctx->allocator || !ctx->allocator->alloc) return CF_STATUS_ERROR;
  out->storage_type = storage_type;
  out->size = size;
  if (size == 0) {
    out->data = NULL;
    return CF_STATUS_OK;
  }
  void* mem = ctx->allocator->alloc(size, ctx->allocator->user_data);
  if (!mem) return CF_STATUS_ERROR;
  if (data) {
    memcpy(mem, data, size);
  }
  out->data = mem;
  return CF_STATUS_OK;
}

static inline CfStatusCode cf_out_string(CfCallContext* ctx, CfValueHandle* out, const char* value) {
  size_t size = value ? strlen(value) : 0;
  return cf_out_bytes(ctx, out, CF_STORAGE_STRING, value, size);
}

static inline CfStatusCode cf_out_f64(CfCallContext* ctx, CfValueHandle* out, double value) {
  return cf_out_bytes(ctx, out, CF_STORAGE_F64, &value, sizeof(double));
}

static inline CfStatusCode cf_out_i64(CfCallContext* ctx, CfValueHandle* out, int64_t value) {
  return cf_out_bytes(ctx, out, CF_STORAGE_I64, &value, sizeof(int64_t));
}

static inline CfStatusCode cf_out_bool(CfCallContext* ctx, CfValueHandle* out, int value) {
  unsigned char byte = value ? 1 : 0;
  return cf_out_bytes(ctx, out, CF_STORAGE_BOOL, &byte, sizeof(unsigned char));
}

static inline CfStatusCode cf_validation_append(CfValidationReport* report,
                                                CfValidationSeverity severity,
                                                const char* message,
                                                const char* parameter_key,
                                                const char* port_key) {
  if (!report || !report->allocator || !report->allocator->alloc) return CF_STATUS_ERROR;
  if (report->issue_count >= report->issue_capacity) {
    size_t new_cap = report->issue_capacity == 0 ? 4 : report->issue_capacity * 2;
    CfValidationIssue* mem = (CfValidationIssue*)report->allocator->alloc(sizeof(CfValidationIssue) * new_cap,
                                                                         report->allocator->user_data);
    if (!mem) return CF_STATUS_ERROR;
    if (report->issues && report->issue_count > 0) {
      memcpy(mem, report->issues, sizeof(CfValidationIssue) * report->issue_count);
    }
    /* Free old buffer if allocator provides free (arena allocators may leave it null). */
    if (report->issues && report->allocator->free) {
      report->allocator->free(report->issues, report->allocator->user_data);
    }
    report->issues = mem;
    report->issue_capacity = new_cap;
  }
  CfValidationIssue* issue = &report->issues[report->issue_count++];
  issue->severity = severity;
  issue->message = message;
  issue->parameter_key = parameter_key;
  issue->port_key = port_key;

  if (message) {
    size_t msg_len = strlen(message);
    char* msg_copy = (char*)report->allocator->alloc(msg_len + 1, report->allocator->user_data);
    if (msg_copy) {
      memcpy(msg_copy, message, msg_len + 1);
      issue->message = msg_copy;
    }
  }
  if (parameter_key) {
    size_t len = strlen(parameter_key);
    char* copy = (char*)report->allocator->alloc(len + 1, report->allocator->user_data);
    if (copy) {
      memcpy(copy, parameter_key, len + 1);
      issue->parameter_key = copy;
    }
  }
  if (port_key) {
    size_t len = strlen(port_key);
    char* copy = (char*)report->allocator->alloc(len + 1, report->allocator->user_data);
    if (copy) {
      memcpy(copy, port_key, len + 1);
      issue->port_key = copy;
    }
  }
  return CF_STATUS_OK;
}

#ifdef __cplusplus
}
#endif

#endif

