/*
 * NOSTR Core Library Implementation - Core Functionality
 * 
 * Self-contained crypto implementation (no external crypto dependencies)
 * 
 * This file contains:
 * - NIP-01: Basic Protocol Flow
 * - NIP-05: Mapping Nostr keys to DNS-based internet identifiers
 * - NIP-06: Key Derivation from Mnemonic
 * - NIP-10: Text Notes (Kind 1)
 * - NIP-13: Proof of Work
 * - NIP-19: Bech32-encoded Entities
 * - General Utilities
 * - Identity Management
 * - Single Relay Communication
 */

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L

#include "nostr_core.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>

// Our self-contained crypto implementation
#include "nostr_crypto.h"

// Our production-ready WebSocket implementation
#include "../nostr_websocket/nostr_websocket_tls.h"

// cJSON for JSON handling
#include "../cjson/cJSON.h"

#ifndef DISABLE_NIP05
#include <curl/curl.h>
#endif

// Forward declarations for bech32 functions (used by NIP-06 functions)
static int convert_bits(uint8_t *out, size_t *outlen, int outbits, const uint8_t *in, size_t inlen, int inbits, int pad);
static int bech32_encode(char *output, const char *hrp, const uint8_t *data, size_t data_len);
static int bech32_decode(const char* input, const char* hrp, unsigned char* data, size_t* data_len);


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                GENERAL UTILITIES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

void nostr_bytes_to_hex(const unsigned char* bytes, size_t len, char* hex) {
    for (size_t i = 0; i < len; i++) {
        sprintf(hex + i * 2, "%02x", bytes[i]);
    }
    hex[len * 2] = '\0';
}

int nostr_hex_to_bytes(const char* hex, unsigned char* bytes, size_t len) {
    if (strlen(hex) != len * 2) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    for (size_t i = 0; i < len; i++) {
        if (sscanf(hex + i * 2, "%02hhx", &bytes[i]) != 1) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    }
    return NOSTR_SUCCESS;
}



////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-01: BASIC PROTOCOL FLOW
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

int nostr_init(void) {
    if (nostr_crypto_init() != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    return NOSTR_SUCCESS;
}

void nostr_cleanup(void) {
    nostr_crypto_cleanup();
}

const char* nostr_strerror(int error_code) {
    switch (error_code) {
        case NOSTR_SUCCESS: return "Success";
        case NOSTR_ERROR_INVALID_INPUT: return "Invalid input";
        case NOSTR_ERROR_CRYPTO_FAILED: return "Cryptographic operation failed";
        case NOSTR_ERROR_MEMORY_FAILED: return "Memory allocation failed";
        case NOSTR_ERROR_IO_FAILED: return "I/O operation failed";
        case NOSTR_ERROR_NETWORK_FAILED: return "Network operation failed";
        case NOSTR_ERROR_NIP04_INVALID_FORMAT: return "NIP-04 invalid format";
        case NOSTR_ERROR_NIP04_DECRYPT_FAILED: return "NIP-04 decryption failed";
        case NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL: return "NIP-04 buffer too small";
        case NOSTR_ERROR_NIP05_INVALID_IDENTIFIER: return "NIP-05: Invalid identifier format";
        case NOSTR_ERROR_NIP05_HTTP_FAILED: return "NIP-05: HTTP request failed";
        case NOSTR_ERROR_NIP05_JSON_PARSE_FAILED: return "NIP-05: JSON parsing failed";
        case NOSTR_ERROR_NIP05_NAME_NOT_FOUND: return "NIP-05: Name not found in .well-known";
        case NOSTR_ERROR_NIP05_PUBKEY_MISMATCH: return "NIP-05: Public key mismatch";
        default: return "Unknown error";
    }
}

cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags, const unsigned char* private_key, time_t timestamp) {
    if (!private_key) {
        return NULL;
    }
    
    if (!content) {
        content = ""; // Default to empty content
    }
    
    // Convert private key to public key
    unsigned char public_key[32];
    if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) {
        return NULL;
    }
    
    // Convert public key to hex
    char pubkey_hex[65];
    nostr_bytes_to_hex(public_key, 32, pubkey_hex);
    
    // Create event structure
    cJSON* event = cJSON_CreateObject();
    if (!event) {
        return NULL;
    }
    
    // Use provided timestamp or current time if timestamp is 0
    time_t event_time = (timestamp == 0) ? time(NULL) : timestamp;
    
    cJSON_AddStringToObject(event, "pubkey", pubkey_hex);
    cJSON_AddNumberToObject(event, "created_at", (double)event_time);
    cJSON_AddNumberToObject(event, "kind", kind);
    
    // Add tags (copy provided tags or create empty array)
    if (tags) {
        cJSON_AddItemToObject(event, "tags", cJSON_Duplicate(tags, 1));
    } else {
        cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
    }
    
    cJSON_AddStringToObject(event, "content", content);
    
    // ============================================================================
    // INLINE SERIALIZATION AND SIGNING LOGIC 
    // ============================================================================
    
    // Get event fields for serialization
    cJSON* pubkey_item = cJSON_GetObjectItem(event, "pubkey");
    cJSON* created_at_item = cJSON_GetObjectItem(event, "created_at");
    cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
    cJSON* tags_item = cJSON_GetObjectItem(event, "tags");
    cJSON* content_item = cJSON_GetObjectItem(event, "content");
    
    if (!pubkey_item || !created_at_item || !kind_item || !tags_item || !content_item) {
        cJSON_Delete(event);
        return NULL;
    }
    
    // Create serialization array: [0, pubkey, created_at, kind, tags, content]
    cJSON* serialize_array = cJSON_CreateArray();
    if (!serialize_array) {
        cJSON_Delete(event);
        return NULL;
    }
    
    cJSON_AddItemToArray(serialize_array, cJSON_CreateNumber(0));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(pubkey_item, 1));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(created_at_item, 1));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(kind_item, 1));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(tags_item, 1));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(content_item, 1));
    
    char* serialize_string = cJSON_PrintUnformatted(serialize_array);
    cJSON_Delete(serialize_array);
    
    if (!serialize_string) {
        cJSON_Delete(event);
        return NULL;
    }
    
    // Hash the serialized event
    unsigned char event_hash[32];
    if (nostr_sha256((const unsigned char*)serialize_string, strlen(serialize_string), event_hash) != 0) {
        free(serialize_string);
        cJSON_Delete(event);
        return NULL;
    }
    
    // Convert hash to hex for event ID
    char event_id[65];
    nostr_bytes_to_hex(event_hash, 32, event_id);
    
    // Sign the hash using ECDSA
    unsigned char signature[64];
    if (nostr_ec_sign(private_key, event_hash, signature) != 0) {
        free(serialize_string);
        cJSON_Delete(event);
        return NULL;
    }
    
    // Convert signature to hex
    char sig_hex[129];
    nostr_bytes_to_hex(signature, 64, sig_hex);
    
    // Add ID and signature to the event
    cJSON_AddStringToObject(event, "id", event_id);
    cJSON_AddStringToObject(event, "sig", sig_hex);
    
    free(serialize_string);
    
    return event;
}



////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-05: MAPPING NOSTR KEYS TO DNS-BASED INTERNET IDENTIFIERS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

#ifndef DISABLE_NIP05

// Maximum sizes for NIP-05 operations
#define NIP05_MAX_URL_SIZE 512
#define NIP05_MAX_RESPONSE_SIZE 8192
#define NIP05_MAX_IDENTIFIER_SIZE 256
#define NIP05_DEFAULT_TIMEOUT 10

// Structure for HTTP response handling
typedef struct {
    char* data;
    size_t size;
    size_t capacity;
} nip05_http_response_t;

/**
 * Callback function for curl to write HTTP response data
 */
static size_t nip05_write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
    nip05_http_response_t* response = (nip05_http_response_t*)userp;
    size_t total_size = size * nmemb;
    
    // Check if we need to expand the buffer
    if (response->size + total_size >= response->capacity) {
        size_t new_capacity = response->capacity * 2;
        if (new_capacity < response->size + total_size + 1) {
            new_capacity = response->size + total_size + 1;
        }
        
        char* new_data = realloc(response->data, new_capacity);
        if (!new_data) {
            return 0; // Out of memory
        }
        
        response->data = new_data;
        response->capacity = new_capacity;
    }
    
    // Append the new data
    memcpy(response->data + response->size, contents, total_size);
    response->size += total_size;
    response->data[response->size] = '\0';
    
    return total_size;
}

/**
 * Parse and validate a NIP-05 identifier into local part and domain
 */
static int nip05_parse_identifier(const char* identifier, char* local_part, char* domain) {
    if (!identifier || !local_part || !domain) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Find the @ symbol
    const char* at_pos = strchr(identifier, '@');
    if (!at_pos) {
        return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
    }
    
    // Extract local part
    size_t local_len = at_pos - identifier;
    if (local_len == 0 || local_len >= 64) {
        return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
    }
    
    strncpy(local_part, identifier, local_len);
    local_part[local_len] = '\0';
    
    // Extract domain
    const char* domain_start = at_pos + 1;
    size_t domain_len = strlen(domain_start);
    if (domain_len == 0 || domain_len >= 256) {
        return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
    }
    
    strcpy(domain, domain_start);
    
    // Validate characters in local part (a-z0-9-_.)
    for (size_t i = 0; i < local_len; i++) {
        char c = local_part[i];
        if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || 
              (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.')) {
            return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
        }
    }
    
    return NOSTR_SUCCESS;
}

/**
 * Make HTTP GET request to a URL
 */
static int nip05_http_get(const char* url, int timeout_seconds, char** response_data) {
    if (!url || !response_data) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    CURL* curl = curl_easy_init();
    if (!curl) {
        return NOSTR_ERROR_NETWORK_FAILED;
    }
    
    nip05_http_response_t response = {0};
    response.capacity = 1024;
    response.data = malloc(response.capacity);
    if (!response.data) {
        curl_easy_cleanup(curl);
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    response.data[0] = '\0';
    
    // Set curl options
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, nip05_write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)(timeout_seconds > 0 ? timeout_seconds : NIP05_DEFAULT_TIMEOUT));
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); // NIP-05 forbids redirects
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "nostr-core/1.0");
    
    // Perform the request
    CURLcode res = curl_easy_perform(curl);
    long response_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
    
    curl_easy_cleanup(curl);
    
    if (res != CURLE_OK) {
        free(response.data);
        return NOSTR_ERROR_NIP05_HTTP_FAILED;
    }
    
    if (response_code != 200) {
        free(response.data);
        return NOSTR_ERROR_NIP05_HTTP_FAILED;
    }
    
    *response_data = response.data;
    return NOSTR_SUCCESS;
}

/**
 * Validate that a hex string is a valid public key
 */
static int nip05_validate_pubkey_hex(const char* hex_pubkey) {
    if (!hex_pubkey) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    size_t len = strlen(hex_pubkey);
    if (len != 64) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Check all characters are valid hex
    for (size_t i = 0; i < len; i++) {
        char c = hex_pubkey[i];
        if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    }
    
    return NOSTR_SUCCESS;
}

/**
 * Parse a .well-known/nostr.json response and extract pubkey and relays for a specific name
 */
int nostr_nip05_parse_well_known(const char* json_response, const char* local_part,
                                 char* pubkey_hex_out, char*** relays, int* relay_count) {
    if (!json_response || !local_part) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Initialize outputs
    if (pubkey_hex_out) {
        pubkey_hex_out[0] = '\0';
    }
    if (relays) {
        *relays = NULL;
    }
    if (relay_count) {
        *relay_count = 0;
    }
    
    // Parse JSON
    cJSON* json = cJSON_Parse(json_response);
    if (!json) {
        return NOSTR_ERROR_NIP05_JSON_PARSE_FAILED;
    }
    
    int result = NOSTR_ERROR_NIP05_NAME_NOT_FOUND;
    
    // Get the "names" object
    cJSON* names = cJSON_GetObjectItem(json, "names");
    if (names && cJSON_IsObject(names)) {
        cJSON* pubkey_item = cJSON_GetObjectItem(names, local_part);
        if (pubkey_item && cJSON_IsString(pubkey_item)) {
            const char* found_pubkey = cJSON_GetStringValue(pubkey_item);
            
            // Validate the public key format
            if (nip05_validate_pubkey_hex(found_pubkey) == NOSTR_SUCCESS) {
                if (pubkey_hex_out) {
                    strcpy(pubkey_hex_out, found_pubkey);
                }
                result = NOSTR_SUCCESS;
                
                // Extract relays if requested
                if (relays && relay_count) {
                    cJSON* relays_obj = cJSON_GetObjectItem(json, "relays");
                    if (relays_obj && cJSON_IsObject(relays_obj)) {
                        cJSON* user_relays = cJSON_GetObjectItem(relays_obj, found_pubkey);
                        if (user_relays && cJSON_IsArray(user_relays)) {
                            int relay_array_size = cJSON_GetArraySize(user_relays);
                            if (relay_array_size > 0) {
                                char** relay_array = malloc(relay_array_size * sizeof(char*));
                                if (relay_array) {
                                    int valid_relays = 0;
                                    for (int i = 0; i < relay_array_size; i++) {
                                        cJSON* relay_item = cJSON_GetArrayItem(user_relays, i);
                                        if (relay_item && cJSON_IsString(relay_item)) {
                                            const char* relay_url = cJSON_GetStringValue(relay_item);
                                            if (relay_url && strlen(relay_url) > 0) {
                                                relay_array[valid_relays] = malloc(strlen(relay_url) + 1);
                                                if (relay_array[valid_relays]) {
                                                    strcpy(relay_array[valid_relays], relay_url);
                                                    valid_relays++;
                                                }
                                            }
                                        }
                                    }
                                    
                                    if (valid_relays > 0) {
                                        *relays = relay_array;
                                        *relay_count = valid_relays;
                                    } else {
                                        free(relay_array);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    cJSON_Delete(json);
    return result;
}

/**
 * Lookup a public key from a NIP-05 identifier
 */
int nostr_nip05_lookup(const char* nip05_identifier, char* pubkey_hex_out,
                       char*** relays, int* relay_count, int timeout_seconds) {
    if (!nip05_identifier) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    char local_part[64];
    char domain[256];
    char url[NIP05_MAX_URL_SIZE];
    
    // Parse the identifier
    int parse_result = nip05_parse_identifier(nip05_identifier, local_part, domain);
    if (parse_result != NOSTR_SUCCESS) {
        return parse_result;
    }
    
    // Construct the .well-known URL
    int url_result = snprintf(url, sizeof(url), "https://%s/.well-known/nostr.json?name=%s", 
                             domain, local_part);
    if (url_result >= (int)sizeof(url) || url_result < 0) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Make HTTP request
    char* response_data = NULL;
    int http_result = nip05_http_get(url, timeout_seconds, &response_data);
    if (http_result != NOSTR_SUCCESS) {
        return http_result;
    }
    
    // Parse the response
    int parse_response_result = nostr_nip05_parse_well_known(response_data, local_part,
                                                            pubkey_hex_out, relays, relay_count);
    
    free(response_data);
    return parse_response_result;
}

/**
 * Verify a NIP-05 identifier against a public key
 */
int nostr_nip05_verify(const char* nip05_identifier, const char* pubkey_hex, 
                       char*** relays, int* relay_count, int timeout_seconds) {
    if (!nip05_identifier || !pubkey_hex) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Validate the input public key format
    if (nip05_validate_pubkey_hex(pubkey_hex) != NOSTR_SUCCESS) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    char found_pubkey[65];
    
    // Lookup the public key for this identifier
    int lookup_result = nostr_nip05_lookup(nip05_identifier, found_pubkey, 
                                          relays, relay_count, timeout_seconds);
    if (lookup_result != NOSTR_SUCCESS) {
        return lookup_result;
    }
    
    // Compare the public keys (case insensitive)
    if (strcasecmp(pubkey_hex, found_pubkey) != 0) {
        // Clean up relays if verification failed
        if (relays && *relays) {
            for (int i = 0; i < (relay_count ? *relay_count : 0); i++) {
                free((*relays)[i]);
            }
            free(*relays);
            *relays = NULL;
        }
        if (relay_count) {
            *relay_count = 0;
        }
        return NOSTR_ERROR_NIP05_PUBKEY_MISMATCH;
    }
    
    return NOSTR_SUCCESS;
}

#else // DISABLE_NIP05

/**
 * Stub implementations when NIP-05 is disabled at compile time
 */
int nostr_nip05_parse_well_known(const char* json_response, const char* local_part,
                                 char* pubkey_hex_out, char*** relays, int* relay_count) {
    (void)json_response;
    (void)local_part;
    (void)pubkey_hex_out;
    (void)relays;
    (void)relay_count;
    return NOSTR_ERROR_NETWORK_FAILED; // NIP-05 disabled at compile time
}

int nostr_nip05_lookup(const char* nip05_identifier, char* pubkey_hex_out,
                       char*** relays, int* relay_count, int timeout_seconds) {
    (void)nip05_identifier;
    (void)pubkey_hex_out;
    (void)relays;
    (void)relay_count;
    (void)timeout_seconds;
    return NOSTR_ERROR_NETWORK_FAILED; // NIP-05 disabled at compile time
}

int nostr_nip05_verify(const char* nip05_identifier, const char* pubkey_hex, 
                       char*** relays, int* relay_count, int timeout_seconds) {
    (void)nip05_identifier;
    (void)pubkey_hex;
    (void)relays;
    (void)relay_count;
    (void)timeout_seconds;
    return NOSTR_ERROR_NETWORK_FAILED; // NIP-05 disabled at compile time
}

#endif // DISABLE_NIP05



////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-11: RELAY INFORMATION DOCUMENT
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

#ifndef DISABLE_NIP05 // NIP-11 uses the same HTTP infrastructure as NIP-05

// Maximum sizes for NIP-11 operations
#define NIP11_MAX_URL_SIZE 512
#define NIP11_MAX_RESPONSE_SIZE 16384
#define NIP11_DEFAULT_TIMEOUT 10

// Forward declaration
void nostr_nip11_relay_info_free(nostr_relay_info_t* info);

/**
 * Convert WebSocket URL to HTTP URL for NIP-11 document retrieval
 */
static char* nip11_ws_to_http_url(const char* ws_url) {
    if (!ws_url) {
        return NULL;
    }
    
    size_t url_len = strlen(ws_url);
    char* http_url = malloc(url_len + 10); // Extra space for protocol change
    if (!http_url) {
        return NULL;
    }
    
    // Convert ws:// to http:// and wss:// to https://
    if (strncmp(ws_url, "ws://", 5) == 0) {
        sprintf(http_url, "http://%s", ws_url + 5);
    } else if (strncmp(ws_url, "wss://", 6) == 0) {
        sprintf(http_url, "https://%s", ws_url + 6);
    } else {
        // Assume it's already HTTP(S) or add https:// as default
        if (strncmp(ws_url, "http://", 7) == 0 || strncmp(ws_url, "https://", 8) == 0) {
            strcpy(http_url, ws_url);
        } else {
            sprintf(http_url, "https://%s", ws_url);
        }
    }
    
    return http_url;
}

/**
 * Parse supported NIPs array from JSON
 */
static int nip11_parse_supported_nips(cJSON* nips_array, int** nips_out, size_t* count_out) {
    if (!nips_array || !cJSON_IsArray(nips_array)) {
        *nips_out = NULL;
        *count_out = 0;
        return NOSTR_SUCCESS;
    }
    
    int array_size = cJSON_GetArraySize(nips_array);
    if (array_size == 0) {
        *nips_out = NULL;
        *count_out = 0;
        return NOSTR_SUCCESS;
    }
    
    int* nips = malloc(array_size * sizeof(int));
    if (!nips) {
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    int valid_count = 0;
    for (int i = 0; i < array_size; i++) {
        cJSON* nip_item = cJSON_GetArrayItem(nips_array, i);
        if (nip_item && cJSON_IsNumber(nip_item)) {
            nips[valid_count++] = (int)cJSON_GetNumberValue(nip_item);
        }
    }
    
    if (valid_count == 0) {
        free(nips);
        *nips_out = NULL;
        *count_out = 0;
    } else {
        *nips_out = nips;
        *count_out = valid_count;
    }
    
    return NOSTR_SUCCESS;
}

/**
 * Parse string array from JSON
 */
static int nip11_parse_string_array(cJSON* json_array, char*** strings_out, size_t* count_out) {
    if (!json_array || !cJSON_IsArray(json_array)) {
        *strings_out = NULL;
        *count_out = 0;
        return NOSTR_SUCCESS;
    }
    
    int array_size = cJSON_GetArraySize(json_array);
    if (array_size == 0) {
        *strings_out = NULL;
        *count_out = 0;
        return NOSTR_SUCCESS;
    }
    
    char** strings = malloc(array_size * sizeof(char*));
    if (!strings) {
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    int valid_count = 0;
    for (int i = 0; i < array_size; i++) {
        cJSON* string_item = cJSON_GetArrayItem(json_array, i);
        if (string_item && cJSON_IsString(string_item)) {
            const char* str_value = cJSON_GetStringValue(string_item);
            if (str_value && strlen(str_value) > 0) {
                strings[valid_count] = malloc(strlen(str_value) + 1);
                if (strings[valid_count]) {
                    strcpy(strings[valid_count], str_value);
                    valid_count++;
                }
            }
        }
    }
    
    if (valid_count == 0) {
        free(strings);
        *strings_out = NULL;
        *count_out = 0;
    } else {
        *strings_out = strings;
        *count_out = valid_count;
    }
    
    return NOSTR_SUCCESS;
}

/**
 * Helper to safely copy JSON string value
 */
static char* nip11_copy_json_string(cJSON* json_item) {
    if (!json_item || !cJSON_IsString(json_item)) {
        return NULL;
    }
    
    const char* str_value = cJSON_GetStringValue(json_item);
    if (!str_value) {
        return NULL;
    }
    
    char* copy = malloc(strlen(str_value) + 1);
    if (copy) {
        strcpy(copy, str_value);
    }
    
    return copy;
}

/**
 * Parse NIP-11 relay information document from JSON
 */
static int nip11_parse_relay_info(const char* json_response, nostr_relay_info_t* info) {
    if (!json_response || !info) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Initialize structure
    memset(info, 0, sizeof(nostr_relay_info_t));
    
    // Parse JSON
    cJSON* json = cJSON_Parse(json_response);
    if (!json) {
        return NOSTR_ERROR_NIP05_JSON_PARSE_FAILED;
    }
    
    // Parse basic information
    info->basic.name = nip11_copy_json_string(cJSON_GetObjectItem(json, "name"));
    info->basic.description = nip11_copy_json_string(cJSON_GetObjectItem(json, "description"));
    info->basic.pubkey = nip11_copy_json_string(cJSON_GetObjectItem(json, "pubkey"));
    info->basic.contact = nip11_copy_json_string(cJSON_GetObjectItem(json, "contact"));
    info->basic.software = nip11_copy_json_string(cJSON_GetObjectItem(json, "software"));
    info->basic.version = nip11_copy_json_string(cJSON_GetObjectItem(json, "version"));
    
    // Parse supported NIPs
    cJSON* supported_nips = cJSON_GetObjectItem(json, "supported_nips");
    nip11_parse_supported_nips(supported_nips, &info->basic.supported_nips, &info->basic.supported_nips_count);
    
    // Parse limitations (if present)
    cJSON* limitation = cJSON_GetObjectItem(json, "limitation");
    if (limitation && cJSON_IsObject(limitation)) {
        info->has_limitations = 1;
        
        cJSON* item;
        item = cJSON_GetObjectItem(limitation, "max_message_length");
        info->limitations.max_message_length = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "max_subscriptions");
        info->limitations.max_subscriptions = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "max_filters");
        info->limitations.max_filters = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "max_limit");
        info->limitations.max_limit = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "max_subid_length");
        info->limitations.max_subid_length = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "min_prefix");
        info->limitations.min_prefix = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "max_event_tags");
        info->limitations.max_event_tags = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "max_content_length");
        info->limitations.max_content_length = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "min_pow_difficulty");
        info->limitations.min_pow_difficulty = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "auth_required");
        info->limitations.auth_required = (item && cJSON_IsBool(item)) ? (cJSON_IsTrue(item) ? 1 : 0) : -1;
        
        item = cJSON_GetObjectItem(limitation, "payment_required");
        info->limitations.payment_required = (item && cJSON_IsBool(item)) ? (cJSON_IsTrue(item) ? 1 : 0) : -1;
        
        item = cJSON_GetObjectItem(limitation, "restricted_writes");
        info->limitations.restricted_writes = (item && cJSON_IsBool(item)) ? (cJSON_IsTrue(item) ? 1 : 0) : -1;
        
        item = cJSON_GetObjectItem(limitation, "created_at_lower_limit");
        info->limitations.created_at_lower_limit = (item && cJSON_IsNumber(item)) ? (long)cJSON_GetNumberValue(item) : -1;
        
        item = cJSON_GetObjectItem(limitation, "created_at_upper_limit");
        info->limitations.created_at_upper_limit = (item && cJSON_IsNumber(item)) ? (long)cJSON_GetNumberValue(item) : -1;
    }
    
    // Parse relay countries
    cJSON* relay_countries = cJSON_GetObjectItem(json, "relay_countries");
    if (relay_countries && cJSON_IsArray(relay_countries)) {
        info->has_content_limitations = 1;
        nip11_parse_string_array(relay_countries, &info->content_limitations.relay_countries, 
                                &info->content_limitations.relay_countries_count);
    }
    
    // Parse community preferences
    cJSON* language_tags = cJSON_GetObjectItem(json, "language_tags");
    cJSON* tags = cJSON_GetObjectItem(json, "tags");
    cJSON* posting_policy = cJSON_GetObjectItem(json, "posting_policy");
    
    if ((language_tags && cJSON_IsArray(language_tags)) || 
        (tags && cJSON_IsArray(tags)) || 
        (posting_policy && cJSON_IsString(posting_policy))) {
        info->has_community_preferences = 1;
        
        if (language_tags) {
            nip11_parse_string_array(language_tags, &info->community_preferences.language_tags, 
                                    &info->community_preferences.language_tags_count);
        }
        
        if (tags) {
            nip11_parse_string_array(tags, &info->community_preferences.tags, 
                                    &info->community_preferences.tags_count);
        }
        
        if (posting_policy) {
            info->community_preferences.posting_policy = nip11_copy_json_string(posting_policy);
        }
    }
    
    // Parse icon
    cJSON* icon = cJSON_GetObjectItem(json, "icon");
    if (icon && cJSON_IsString(icon)) {
        info->has_icon = 1;
        info->icon.icon = nip11_copy_json_string(icon);
    }
    
    cJSON_Delete(json);
    return NOSTR_SUCCESS;
}

/**
 * Fetch relay information document from a relay URL
 */
int nostr_nip11_fetch_relay_info(const char* relay_url, nostr_relay_info_t** info_out, int timeout_seconds) {
    if (!relay_url || !info_out) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Convert WebSocket URL to HTTP URL
    char* http_url = nip11_ws_to_http_url(relay_url);
    if (!http_url) {
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    // Allocate info structure
    nostr_relay_info_t* info = malloc(sizeof(nostr_relay_info_t));
    if (!info) {
        free(http_url);
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    // Make HTTP request with NIP-11 required header
    CURL* curl = curl_easy_init();
    if (!curl) {
        free(http_url);
        free(info);
        return NOSTR_ERROR_NETWORK_FAILED;
    }
    
    nip05_http_response_t response = {0};
    response.capacity = 1024;
    response.data = malloc(response.capacity);
    if (!response.data) {
        curl_easy_cleanup(curl);
        free(http_url);
        free(info);
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    response.data[0] = '\0';
    
    // Set up headers for NIP-11
    struct curl_slist* headers = NULL;
    headers = curl_slist_append(headers, "Accept: application/nostr+json");
    
    // Set curl options
    curl_easy_setopt(curl, CURLOPT_URL, http_url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, nip05_write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)(timeout_seconds > 0 ? timeout_seconds : NIP11_DEFAULT_TIMEOUT));
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // NIP-11 allows redirects
    curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "nostr-core/1.0");
    
    // Perform the request
    CURLcode res = curl_easy_perform(curl);
    long response_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
    
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(http_url);
    
    if (res != CURLE_OK || response_code != 200) {
        free(response.data);
        free(info);
        return NOSTR_ERROR_NIP05_HTTP_FAILED;
    }
    
    // Parse the relay information
    int parse_result = nip11_parse_relay_info(response.data, info);
    free(response.data);
    
    if (parse_result != NOSTR_SUCCESS) {
        nostr_nip11_relay_info_free(info);
        return parse_result;
    }
    
    *info_out = info;
    return NOSTR_SUCCESS;
}

/**
 * Free relay information structure
 */
void nostr_nip11_relay_info_free(nostr_relay_info_t* info) {
    if (!info) {
        return;
    }
    
    // Free basic info strings
    free(info->basic.name);
    free(info->basic.description);
    free(info->basic.pubkey);
    free(info->basic.contact);
    free(info->basic.software);
    free(info->basic.version);
    free(info->basic.supported_nips);
    
    // Free content limitations
    if (info->has_content_limitations) {
        for (size_t i = 0; i < info->content_limitations.relay_countries_count; i++) {
            free(info->content_limitations.relay_countries[i]);
        }
        free(info->content_limitations.relay_countries);
    }
    
    // Free community preferences
    if (info->has_community_preferences) {
        for (size_t i = 0; i < info->community_preferences.language_tags_count; i++) {
            free(info->community_preferences.language_tags[i]);
        }
        free(info->community_preferences.language_tags);
        
        for (size_t i = 0; i < info->community_preferences.tags_count; i++) {
            free(info->community_preferences.tags[i]);
        }
        free(info->community_preferences.tags);
        
        free(info->community_preferences.posting_policy);
    }
    
    // Free icon
    if (info->has_icon) {
        free(info->icon.icon);
    }
    
    free(info);
}

#else // DISABLE_NIP05

/**
 * Stub implementations when NIP-05 is disabled at compile time
 */
int nostr_nip11_fetch_relay_info(const char* relay_url, nostr_relay_info_t** info_out, int timeout_seconds) {
    (void)relay_url;
    (void)info_out;
    (void)timeout_seconds;
    return NOSTR_ERROR_NETWORK_FAILED; // NIP-11 disabled at compile time
}

void nostr_nip11_relay_info_free(nostr_relay_info_t* info) {
    (void)info;
    // No-op when disabled
}

#endif // DISABLE_NIP05



////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-06: KEY DERIVATION FROM MNEMONIC
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

int nostr_generate_keypair(unsigned char* private_key, unsigned char* public_key) {
    if (!private_key || !public_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Generate random entropy using /dev/urandom
    FILE* urandom = fopen("/dev/urandom", "rb");
    if (!urandom) {
        return NOSTR_ERROR_IO_FAILED;
    }
    
    if (fread(private_key, 1, 32, urandom) != 32) {
        fclose(urandom);
        return NOSTR_ERROR_IO_FAILED;
    }
    fclose(urandom);
    
    // Validate private key
    if (nostr_ec_private_key_verify(private_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Generate public key from private key (already x-only for NOSTR)
    if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    return NOSTR_SUCCESS;
}

int nostr_generate_mnemonic_and_keys(char* mnemonic, size_t mnemonic_size, 
                                    int account, unsigned char* private_key, 
                                    unsigned char* public_key) {
    if (!mnemonic || mnemonic_size < 256 || !private_key || !public_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Generate entropy for 12-word mnemonic
    unsigned char entropy[16];
    FILE* urandom = fopen("/dev/urandom", "rb");
    if (!urandom) {
        return NOSTR_ERROR_IO_FAILED;
    }
    
    if (fread(entropy, 1, sizeof(entropy), urandom) != sizeof(entropy)) {
        fclose(urandom);
        return NOSTR_ERROR_IO_FAILED;
    }
    fclose(urandom);
    
    // Generate mnemonic from entropy
    if (nostr_bip39_mnemonic_from_bytes(entropy, sizeof(entropy), mnemonic) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Derive keys from the generated mnemonic
    return nostr_derive_keys_from_mnemonic(mnemonic, account, private_key, public_key);
}

int nostr_derive_keys_from_mnemonic(const char* mnemonic, int account,
                                   unsigned char* private_key, unsigned char* public_key) {
    if (!mnemonic || !private_key || !public_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Validate mnemonic
    if (nostr_bip39_mnemonic_validate(mnemonic) != 0) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Convert mnemonic to seed
    unsigned char seed[64];
    if (nostr_bip39_mnemonic_to_seed(mnemonic, "", seed, sizeof(seed)) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Derive master key from seed
    nostr_hd_key_t master_key;
    if (nostr_bip32_key_from_seed(seed, sizeof(seed), &master_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // NIP-06 path: m/44'/1237'/account'/0/0
    nostr_hd_key_t derived_key;
    uint32_t path[] = {
        0x80000000 + 44,    // 44' (hardened)
        0x80000000 + 1237,  // 1237' (hardened)
        0x80000000 + account, // account' (hardened)
        0,                   // 0 (not hardened)
        0                    // 0 (not hardened)
    };
    
    if (nostr_bip32_derive_path(&master_key, path, sizeof(path) / sizeof(path[0]), &derived_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Extract private key and public key
    memcpy(private_key, derived_key.private_key, 32);
    memcpy(public_key, derived_key.public_key + 1, 32); // Remove compression prefix for x-only
    
    return NOSTR_SUCCESS;
}

int nostr_key_to_bech32(const unsigned char* key, const char* hrp, char* output) {
    if (!key || !hrp || !output) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    uint8_t conv[64];
    size_t conv_len;
    
    if (!convert_bits(conv, &conv_len, 5, key, 32, 8, 1)) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    if (!bech32_encode(output, hrp, conv, conv_len)) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    return NOSTR_SUCCESS;
}

nostr_input_type_t nostr_detect_input_type(const char* input) {
    if (!input || strlen(input) == 0) {
        return NOSTR_INPUT_UNKNOWN;
    }
    
    size_t len = strlen(input);
    
    // Check for bech32 nsec
    if (len > 5 && strncmp(input, "nsec1", 5) == 0) {
        return NOSTR_INPUT_NSEC_BECH32;
    }
    
    // Check for hex nsec (64 characters, all hex)
    if (len == 64) {
        int is_hex = 1;
        for (size_t i = 0; i < len; i++) {
            if (!isxdigit(input[i])) {
                is_hex = 0;
                break;
            }
        }
        if (is_hex) {
            return NOSTR_INPUT_NSEC_HEX;
        }
    }
    
    // Check for mnemonic (space-separated words)
    int word_count = 0;
    char temp[1024];
    strncpy(temp, input, sizeof(temp) - 1);
    temp[sizeof(temp) - 1] = '\0';
    
    char* token = strtok(temp, " ");
    while (token != NULL) {
        word_count++;
        token = strtok(NULL, " ");
    }
    
    // BIP39 mnemonics are typically 12, 18, or 24 words
    if (word_count >= 12 && word_count <= 24) {
        return NOSTR_INPUT_MNEMONIC;
    }
    
    return NOSTR_INPUT_UNKNOWN;
}

int nostr_decode_nsec(const char* input, unsigned char* private_key) {
    if (!input || !private_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    nostr_input_type_t type = nostr_detect_input_type(input);
    
    if (type == NOSTR_INPUT_NSEC_HEX) {
        if (nostr_hex_to_bytes(input, private_key, 32) != NOSTR_SUCCESS) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    } else if (type == NOSTR_INPUT_NSEC_BECH32) {
        size_t decoded_len;
        if (!bech32_decode(input, "nsec", private_key, &decoded_len)) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
        if (decoded_len != 32) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    } else {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Validate the private key
    if (nostr_ec_private_key_verify(private_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    return NOSTR_SUCCESS;
}

int nostr_decode_npub(const char* input, unsigned char* public_key) {
    if (!input || !public_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    nostr_input_type_t type = nostr_detect_input_type(input);
    
    if (type == NOSTR_INPUT_NSEC_HEX) {  // Actually public key hex
        if (nostr_hex_to_bytes(input, public_key, 32) != NOSTR_SUCCESS) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    } else if (strncmp(input, "npub1", 4) == 0) {  // Bech32 npub
        size_t decoded_len;
        if (!bech32_decode(input, "npub", public_key, &decoded_len)) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
        if (decoded_len != 32) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    } else {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Validate the public key (could add nostr_ec_public_key_verify if it exists)
    
    return NOSTR_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-13: PROOF OF WORK
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/**
 * Count leading zero bits in a hash (NIP-13 reference implementation)
 */
static int zero_bits(unsigned char b) {
    int n = 0;
    
    if (b == 0)
        return 8;
    
    while (b >>= 1)
        n++;
    
    return 7-n;
}

/**
 * Find the number of leading zero bits in a hash (NIP-13 reference implementation)
 */
static int count_leading_zero_bits(unsigned char *hash) {
    int bits, total, i;
    for (i = 0, total = 0; i < 32; i++) {
        bits = zero_bits(hash[i]);
        total += bits;
        if (bits != 8)
            break;
    }
    return total;
}


/**
 * Add or update nonce tag with target difficulty
 */
static int update_nonce_tag_with_difficulty(cJSON* tags, uint64_t nonce, int target_difficulty) {
    if (!tags) return -1;
    
    // Look for existing nonce tag and remove it
    cJSON* tag = NULL;
    int index = 0;
    cJSON_ArrayForEach(tag, tags) {
        if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
            cJSON* tag_type = cJSON_GetArrayItem(tag, 0);
            if (tag_type && cJSON_IsString(tag_type) && 
                strcmp(cJSON_GetStringValue(tag_type), "nonce") == 0) {
                // Remove existing nonce tag
                cJSON_DetachItemFromArray(tags, index);
                cJSON_Delete(tag);
                break;
            }
        }
        index++;
    }
    
    // Add new nonce tag with format: ["nonce", "<nonce>", "<target_difficulty>"]
    cJSON* nonce_tag = cJSON_CreateArray();
    if (!nonce_tag) return -1;
    
    char nonce_str[32];
    char difficulty_str[16];
    snprintf(nonce_str, sizeof(nonce_str), "%llu", (unsigned long long)nonce);
    snprintf(difficulty_str, sizeof(difficulty_str), "%d", target_difficulty);
    
    cJSON_AddItemToArray(nonce_tag, cJSON_CreateString("nonce"));
    cJSON_AddItemToArray(nonce_tag, cJSON_CreateString(nonce_str));
    cJSON_AddItemToArray(nonce_tag, cJSON_CreateString(difficulty_str));
    
    cJSON_AddItemToArray(tags, nonce_tag);
    return 0;
}

/**
 * Helper function to replace event content with successful PoW result
 */
static void replace_event_content(cJSON* target_event, cJSON* source_event) {
    // Remove old fields
    cJSON_DeleteItemFromObject(target_event, "id");
    cJSON_DeleteItemFromObject(target_event, "sig");
    cJSON_DeleteItemFromObject(target_event, "tags");
    cJSON_DeleteItemFromObject(target_event, "created_at");
    
    // Copy new fields from successful event
    cJSON* id = cJSON_GetObjectItem(source_event, "id");
    cJSON* sig = cJSON_GetObjectItem(source_event, "sig");
    cJSON* tags = cJSON_GetObjectItem(source_event, "tags");
    cJSON* created_at = cJSON_GetObjectItem(source_event, "created_at");
    
    if (id) cJSON_AddItemToObject(target_event, "id", cJSON_Duplicate(id, 1));
    if (sig) cJSON_AddItemToObject(target_event, "sig", cJSON_Duplicate(sig, 1));
    if (tags) cJSON_AddItemToObject(target_event, "tags", cJSON_Duplicate(tags, 1));
    if (created_at) cJSON_AddItemToObject(target_event, "created_at", cJSON_Duplicate(created_at, 1));
}

/**
 * Add NIP-13 Proof of Work to an event
 * 
 * @param event The event to add proof of work to
 * @param private_key The private key for re-signing the event
 * @param target_difficulty Target number of leading zero bits (default: 4 if 0)
 * @param max_attempts Maximum number of mining attempts (default: 10,000,000 if <= 0)
 * @param progress_report_interval How often to call progress callback (default: 10,000 if <= 0)
 * @param timestamp_update_interval How often to update timestamp (default: 10,000 if <= 0)
 * @param progress_callback Optional callback for mining progress
 * @param user_data User data for progress callback
 * @return NOSTR_SUCCESS on success, error code on failure
 */
int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key, 
                           int target_difficulty, int max_attempts,
                           int progress_report_interval, int timestamp_update_interval,
                           void (*progress_callback)(int current_difficulty, uint64_t nonce, void* user_data),
                           void* user_data) {
    if (!event || !private_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Set default difficulty if not specified (but allow 0 to disable PoW)
    if (target_difficulty < 0) {
        target_difficulty = 4;
    }
    
    // If target_difficulty is 0, skip proof of work entirely
    if (target_difficulty == 0) {
        return NOSTR_SUCCESS;
    }
    
    // Set default values for parameters
    if (max_attempts <= 0) {
        max_attempts = 10000000;  // 10 million default
    }
    if (progress_report_interval <= 0) {
        progress_report_interval = 10000;  // Every 10,000 attempts
    }
    if (timestamp_update_interval <= 0) {
        timestamp_update_interval = 10000;  // Every 10,000 attempts
    }
    
    // Extract event data for reconstruction
    cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
    cJSON* content_item = cJSON_GetObjectItem(event, "content");
    cJSON* created_at_item = cJSON_GetObjectItem(event, "created_at");
    cJSON* tags_item = cJSON_GetObjectItem(event, "tags");
    
    if (!kind_item || !content_item || !created_at_item || !tags_item) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    int kind = (int)cJSON_GetNumberValue(kind_item);
    const char* content = cJSON_GetStringValue(content_item);
    time_t original_timestamp = (time_t)cJSON_GetNumberValue(created_at_item);
    
    uint64_t nonce = 0;
    int attempts = 0;
    time_t current_timestamp = original_timestamp;
    
    // PoW difficulty tracking variables
    int best_difficulty_this_round = 0;
    int best_difficulty_overall = 0;
    
    // Mining loop
    while (attempts < max_attempts) {
        // Update timestamp based on timestamp_update_interval
        if (attempts % timestamp_update_interval == 0) {
            current_timestamp = time(NULL);
#ifdef ENABLE_DEBUG_LOGGING
            FILE* f = fopen("debug.log", "a"); 
            if (f) { 
                fprintf(f, "PoW mining: %d attempts, best this round: %d, overall best: %d, goal: %d\n", 
                        attempts, best_difficulty_this_round, best_difficulty_overall, target_difficulty); 
                fclose(f); 
            }
#endif
            // Reset best difficulty for the new round
            best_difficulty_this_round = 0;
        }
        
        // Call progress callback at specified intervals
        if (progress_callback && (attempts % progress_report_interval == 0)) {
            progress_callback(best_difficulty_overall, nonce, user_data);
        }
        
        // Create working copy of tags and add nonce
        cJSON* working_tags = cJSON_Duplicate(tags_item, 1);
        if (!working_tags) {
            return NOSTR_ERROR_MEMORY_FAILED;
        }
        
        if (update_nonce_tag_with_difficulty(working_tags, nonce, target_difficulty) != 0) {
            cJSON_Delete(working_tags);
            return NOSTR_ERROR_CRYPTO_FAILED;
        }
        
        // Create and sign new event with current nonce
        cJSON* test_event = nostr_create_and_sign_event(kind, content, working_tags, 
                                                       private_key, current_timestamp);
        cJSON_Delete(working_tags);
        
        if (!test_event) {
            return NOSTR_ERROR_CRYPTO_FAILED;
        }
        
        // Check PoW difficulty
        cJSON* id_item = cJSON_GetObjectItem(test_event, "id");
        if (!id_item || !cJSON_IsString(id_item)) {
            cJSON_Delete(test_event);
            return NOSTR_ERROR_CRYPTO_FAILED;
        }
        
        const char* event_id = cJSON_GetStringValue(id_item);
        unsigned char hash[32];
        if (nostr_hex_to_bytes(event_id, hash, 32) != NOSTR_SUCCESS) {
            cJSON_Delete(test_event);
            return NOSTR_ERROR_CRYPTO_FAILED;
        }
        
        // Count leading zero bits using NIP-13 method
        int current_difficulty = count_leading_zero_bits(hash);
        
        // Update difficulty tracking
        if (current_difficulty > best_difficulty_this_round) {
            best_difficulty_this_round = current_difficulty;
        }
        if (current_difficulty > best_difficulty_overall) {
            best_difficulty_overall = current_difficulty;
        }
        
        // Check if we've reached the target
        if (current_difficulty >= target_difficulty) {
#ifdef ENABLE_DEBUG_LOGGING
            FILE* f = fopen("debug.log", "a"); 
            if (f) { 
                fprintf(f, "PoW SUCCESS: Found difficulty %d (target %d) at nonce %llu after %d attempts\n", 
                        current_difficulty, target_difficulty, (unsigned long long)nonce, attempts + 1);
                
                // Print the final event JSON
                char* event_json = cJSON_Print(test_event);
                if (event_json) {
                    fprintf(f, "Final event: %s\n", event_json);
                    free(event_json);
                }
                fclose(f); 
            }
#endif
            
            // Copy successful result back to input event
            replace_event_content(event, test_event);
            cJSON_Delete(test_event);
            return NOSTR_SUCCESS;
        }
        
        cJSON_Delete(test_event);
        nonce++;
        attempts++;
    }
    
#ifdef ENABLE_DEBUG_LOGGING
    // Debug logging - failure
    FILE* f = fopen("debug.log", "a"); 
    if (f) { 
        fprintf(f, "PoW FAILED: Mining failed after %d attempts\n", max_attempts); 
        fclose(f); 
    }
#endif
    
    // If we reach here, we've exceeded max attempts
    return NOSTR_ERROR_CRYPTO_FAILED;
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-19: BECH32-ENCODED ENTITIES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

#define BECH32_CONST 1

static const char bech32_charset[] = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";

static const int8_t bech32_charset_rev[128] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    15, -1, 10, 17, 21, 20, 26, 30,  7,  5, -1, -1, -1, -1, -1, -1,
    -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
    1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1,
    -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
    1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1
};

static uint32_t bech32_polymod_step(uint32_t pre) {
    uint8_t b = pre >> 25;
    return ((pre & 0x1FFFFFF) << 5) ^
           (-((b >> 0) & 1) & 0x3b6a57b2UL) ^
           (-((b >> 1) & 1) & 0x26508e6dUL) ^
           (-((b >> 2) & 1) & 0x1ea119faUL) ^
           (-((b >> 3) & 1) & 0x3d4233ddUL) ^
           (-((b >> 4) & 1) & 0x2a1462b3UL);
}

static int convert_bits(uint8_t *out, size_t *outlen, int outbits, const uint8_t *in, size_t inlen, int inbits, int pad) {
    uint32_t val = 0;
    int bits = 0;
    uint32_t maxv = (((uint32_t)1) << outbits) - 1;
    *outlen = 0;
    while (inlen--) {
        val = (val << inbits) | *(in++);
        bits += inbits;
        while (bits >= outbits) {
            bits -= outbits;
            out[(*outlen)++] = (val >> bits) & maxv;
        }
    }
    if (pad) {
        if (bits) {
            out[(*outlen)++] = (val << (outbits - bits)) & maxv;
        }
    } else if (((val << (outbits - bits)) & maxv) || bits >= inbits) {
        return 0;
    }
    return 1;
}

static int bech32_encode(char *output, const char *hrp, const uint8_t *data, size_t data_len) {
    uint32_t chk = 1;
    size_t i, hrp_len = strlen(hrp);
    
    for (i = 0; i < hrp_len; ++i) {
        int ch = hrp[i];
        if (ch < 33 || ch > 126) return 0;
        if (ch >= 'A' && ch <= 'Z') return 0;
        chk = bech32_polymod_step(chk) ^ (ch >> 5);
    }
    
    chk = bech32_polymod_step(chk);
    for (i = 0; i < hrp_len; ++i) {
        chk = bech32_polymod_step(chk) ^ (hrp[i] & 0x1f);
        *(output++) = hrp[i];
    }
    
    *(output++) = '1';
    for (i = 0; i < data_len; ++i) {
        if (*data >> 5) return 0;
        chk = bech32_polymod_step(chk) ^ (*data);
        *(output++) = bech32_charset[*(data++)];
    }
    
    for (i = 0; i < 6; ++i) {
        chk = bech32_polymod_step(chk);
    }
    
    chk ^= BECH32_CONST;
    for (i = 0; i < 6; ++i) {
        *(output++) = bech32_charset[(chk >> ((5 - i) * 5)) & 0x1f];
    }
    
    *output = 0;
    return 1;
}

static int bech32_decode(const char* input, const char* hrp, unsigned char* data, size_t* data_len) {
    if (!input || !hrp || !data || !data_len) {
        return 0;
    }
    
    size_t input_len = strlen(input);
    size_t hrp_len = strlen(hrp);
    
    if (input_len < hrp_len + 7) return 0;
    if (strncmp(input, hrp, hrp_len) != 0) return 0;
    if (input[hrp_len] != '1') return 0;
    
    const char* data_part = input + hrp_len + 1;
    size_t data_part_len = input_len - hrp_len - 1;
    
    uint8_t values[256];
    for (size_t i = 0; i < data_part_len; i++) {
        unsigned char c = (unsigned char)data_part[i];
        if (c >= 128) return 0;
        int8_t val = bech32_charset_rev[c];
        if (val == -1) return 0;
        values[i] = (uint8_t)val;
    }
    
    if (data_part_len < 6) return 0;
    
    uint32_t chk = 1;
    for (size_t i = 0; i < hrp_len; i++) {
        chk = bech32_polymod_step(chk) ^ (hrp[i] >> 5);
    }
    chk = bech32_polymod_step(chk);
    for (size_t i = 0; i < hrp_len; i++) {
        chk = bech32_polymod_step(chk) ^ (hrp[i] & 0x1f);
    }
    for (size_t i = 0; i < data_part_len; i++) {
        chk = bech32_polymod_step(chk) ^ values[i];
    }
    
    if (chk != BECH32_CONST) return 0;
    
    size_t payload_len = data_part_len - 6;
    size_t decoded_len;
    if (!convert_bits(data, &decoded_len, 8, values, payload_len, 5, 0)) {
        return 0;
    }
    
    *data_len = decoded_len;
    return 1;
}
