#pragma once

#ifdef __cplusplus
#include <cstddef>
#include <cstdint>
extern "C" {
#else
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#endif

#if defined(_WIN32)
#define MOD_EXPORT __declspec(dllexport)
#else
#define MOD_EXPORT __attribute__((visibility("default")))
#endif

#ifdef __cplusplus
#define MOD_EXTERN_C extern "C"
#else
#define MOD_EXTERN_C extern
#endif

#ifdef __cplusplus
#define MOD_DECLARE_SERVICE(                                                                      \
    service_type, variable, service_id_value, major_value, minor_value)                           \
    MOD_EXTERN_C const service_type* variable;                                                    \
    template <>                                                                                   \
    struct mods::ServiceTraits<service_type> {                                                    \
        static constexpr const char* id = service_id_value;                                       \
        static constexpr uint16_t major_version = major_value;                                    \
        static constexpr uint16_t minor_version = minor_value;                                    \
    }
#else
#define MOD_DECLARE_SERVICE(                                                                      \
    service_type, variable, service_id_value, major_value, minor_value)                           \
    MOD_EXTERN_C const service_type* variable
#endif

#define MOD_ABI_VERSION 1u
#define MOD_ERROR_MESSAGE_SIZE 512u

typedef struct ModContext ModContext;

typedef enum ModResult {
    MOD_OK = 0,
    MOD_ERROR = 1,
    MOD_UNAVAILABLE = 2,
    MOD_UNSUPPORTED = 3,
    MOD_CONFLICT = 4,
    MOD_INVALID_ARGUMENT = 5,
} ModResult;

static_assert(sizeof(ModResult) == 4,
    "mod SDK enums must be int-sized; do not build mods with -fshort-enums");

typedef struct ModError {
    uint32_t struct_size;
    ModResult code;
    char message[MOD_ERROR_MESSAGE_SIZE];
} ModError;

#define MOD_ERROR_INIT {sizeof(ModError), MOD_OK, {0}}

/*
 * Opaque per-mod context, populated by the host before mod_initialize is called.
 * Pass it as the first argument to every service call; it identifies the calling
 * mod for attribution (logging, resource lookup, hook ownership, etc.).
 */
MOD_EXPORT extern ModContext* mod_ctx;

/*
 * Service versioning contract:
 *
 * A service is a struct of function pointers, beginning with a ServiceHeader.
 * Compatibility is tracked with a major/minor version pair:
 *
 * - A major version bump is a breaking change. Different majors are distinct
 *   services; the registry never matches an import against a different major.
 * - A minor version bump may only append fields to the end of the struct.
 *   Existing fields must keep their offsets and semantics.
 *
 * Providers: exporting minor N means every function pointer introduced at or
 * below N is populated (non-NULL). struct_size reflects the compiled struct.
 *
 * Importers: importing with min_minor_version N guarantees (enforced at load
 * time) that the resolved service is at least minor N, so any field introduced
 * at or below N may be used unconditionally, with no availability checks.
 * Fields newer than the declared min_minor_version must be gated behind
 * SERVICE_HAS plus a NULL check on the pointer itself.
 *
 * Load ordering: a manifest import of another mod's service (required or
 * optional) guarantees that the provider's mod_initialize completed before the
 * importer's runs, and deferred services published during the provider's
 * initialization resolve into import slots just like static exports. If a
 * provider fails to load, mods that required its services fail in turn. Mods
 * whose required imports form a cycle all fail to load; a cycle involving an
 * optional import is broken by dropping the ordering guarantee (not the
 * resolution) of that optional import. Dynamic lookups via
 * HostService::get_service carry no ordering guarantee: they see whatever has
 * been published at call time.
 */
typedef struct ServiceHeader {
    uint32_t struct_size;
    uint16_t major_version;
    uint16_t minor_version;
} ServiceHeader;

#define SERVICE_HEADER(service_type, major, minor) {sizeof(service_type), (major), (minor)}

#define SERVICE_HAS(service, service_type, field)                                                  \
    ((service) != NULL &&                                                                          \
        (service)->header.struct_size >=                                                           \
            (uint32_t)(offsetof(service_type, field) + sizeof(((service_type*)0)->field)))

typedef enum ServiceImportFlags {
    SERVICE_IMPORT_REQUIRED = 0u,
    SERVICE_IMPORT_OPTIONAL = 1u << 0u,
} ServiceImportFlags;

typedef enum ServiceExportFlags {
    SERVICE_EXPORT_STATIC = 0u,
    SERVICE_EXPORT_DEFERRED = 1u << 0u,
} ServiceExportFlags;

/*
 * Mod metadata records.
 *
 * A mod's manifest is a sequence of records in a dedicated section of the native library ("modmeta"
 * on ELF, "__DATA,__modmeta" on Mach-O, "modmeta$a/$d/$z" on PE).
 *
 * The records are pure data. Every string is inline and NUL-terminated. Fields documented as
 * runtime-only hold relocated pointers that are meaningless on disk; static parsers recover their
 * targets from the file's relocation/bind entries instead.
 *
 * Layout rules:
 * - Little-endian, 8-byte aligned; every record size is a multiple of 8.
 * - Parsers must skip all-zero 8-byte units (linker padding) and records of unknown kind.
 * - Exactly one MOD_META_HEADER record per library.
 *
 * The IMPORT_SERVICE/EXPORT_SERVICE/DEFINE_HOOK macros emit these records; mods do not construct
 * them by hand.
 */

#define MOD_META_SERVICE_ID_SIZE 64u

/* Records are 8-byte aligned so the linker packs them without padding; most are naturally
 * aligned by their pointer fields, the alignment below covers the rest. */
#if defined(__cplusplus)
#define MOD_META_ALIGN alignas(8)
#elif defined(_MSC_VER)
#define MOD_META_ALIGN __declspec(align(8))
#else
#define MOD_META_ALIGN __attribute__((aligned(8)))
#endif

typedef enum ModMetaKind {
    MOD_META_PAD = 0,
    MOD_META_HEADER = 1,
    MOD_META_IMPORT = 2,
    MOD_META_EXPORT = 3,
    MOD_META_HOOK_FN = 4,
    MOD_META_HOOK_MEM = 5,
    MOD_META_HOOK_NAME = 6,
    MOD_META_HOOK_MEM_EXT = 7,
} ModMetaKind;

typedef struct ModMetaRecord {
    uint16_t size; /* total record size in bytes, a multiple of 8 */
    uint8_t kind;  /* ModMetaKind */
    uint8_t flags; /* ServiceImportFlags / ServiceExportFlags for imports/exports */
} ModMetaRecord;

typedef struct ModMetaServiceId {
    char chars[MOD_META_SERVICE_ID_SIZE]; /* NUL-terminated */
} ModMetaServiceId;

typedef struct MOD_META_ALIGN ModMetaHeader {
    ModMetaRecord rec;
    uint32_t abi_version;
} ModMetaHeader;

static_assert(sizeof(ModMetaHeader) == 8);

typedef struct MOD_META_ALIGN ModMetaImport {
    ModMetaRecord rec;
    uint16_t major_version;
    uint16_t min_minor_version;
    void* slot; /* runtime only */
    ModMetaServiceId service_id;
} ModMetaImport;

static_assert(sizeof(ModMetaImport) == 16 + MOD_META_SERVICE_ID_SIZE);

typedef struct MOD_META_ALIGN ModMetaExport {
    ModMetaRecord rec;
    uint16_t major_version;
    uint16_t minor_version;
    const void* service; /* runtime only */
    ModMetaServiceId service_id;
} ModMetaExport;

static_assert(sizeof(ModMetaExport) == 16 + MOD_META_SERVICE_ID_SIZE);

/* Hook on a function named at link time: `target` carries the &fn relocation. */
typedef struct MOD_META_ALIGN ModMetaHookFn {
    ModMetaRecord rec;
    uint32_t reserved;
    void* target;   /* runtime only */
    void* resolved; /* runtime only */
} ModMetaHookFn;

static_assert(sizeof(ModMetaHookFn) == 24);

/*
 * Hook on a member function: `pmf` holds the compiler's pointer-to-member representation
 * (non-virtual: a function address relocation; virtual Itanium/AAPCS: literal slot words). Two
 * NUL-terminated strings follow `resolved`: the class vtable symbol (empty if the class name is not
 * representable), then the stringified target for tooling display.
 */
#define MOD_META_HOOK_MEM_CAPACITY 16u
typedef struct MOD_META_ALIGN ModMetaHookMem {
    ModMetaRecord rec;
    uint32_t reserved;
    unsigned char pmf[MOD_META_HOOK_MEM_CAPACITY];
    void* resolved; /* runtime only */
} ModMetaHookMem;

static_assert(sizeof(ModMetaHookMem) == 32);

/*
 * Extended member-function hook record. The Microsoft x64 ABI uses a 24-byte member pointer for
 * classes whose inheritance model was fixed while the class was incomplete. Keep this a distinct
 * record kind so loaders can continue to consume MOD_META_HOOK_MEM's original 16-byte layout.
 *
 * MSVC cannot constant-initialize that member pointer, so materialize points to a compiler-
 * generated helper that writes its native representation to a caller-provided buffer. The record
 * itself, including its trailing names and the helper relocation, remains constant-initialized and
 * available to static tooling. pmf_size is the number of bytes the helper writes.
 */
#define MOD_META_HOOK_MEM_EXT_CAPACITY 24u
typedef void (*ModMetaHookMemMaterializeFn)(unsigned char* out_pmf);
typedef struct MOD_META_ALIGN ModMetaHookMemExt {
    ModMetaRecord rec;
    uint32_t pmf_size;
    ModMetaHookMemMaterializeFn materialize;
    void* resolved; /* runtime only */
} ModMetaHookMemExt;

static_assert(sizeof(ModMetaHookMemExt) == 24);

/*
 * Hook on a function by symbol name, for targets that cannot be named in C++ (file-local statics,
 * private members). One NUL-terminated string follows `resolved`; it may be either the platform
 * mangled name or the demangled qualified display name.
 */
typedef struct MOD_META_ALIGN ModMetaHookName {
    ModMetaRecord rec;
    uint32_t reserved;
    void* resolved; /* runtime only */
} ModMetaHookName;

static_assert(sizeof(ModMetaHookName) == 16);

typedef struct ModMeta {
    uint32_t struct_size;
    const void* records_begin;
    const void* records_end;
} ModMeta;

MOD_EXPORT extern const ModMeta mod_meta;

typedef ModResult (*ModInitializeFn)(ModError* out_error);
typedef ModResult (*ModUpdateFn)(ModError* out_error);
typedef ModResult (*ModShutdownFn)(ModError* out_error);

MOD_EXPORT ModResult mod_initialize(ModError* out_error);
MOD_EXPORT ModResult mod_update(ModError* out_error);
MOD_EXPORT ModResult mod_shutdown(ModError* out_error);

#ifdef __cplusplus
}
#endif
