// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
// Copyright 2025 TimeHexOn & foxhop & russell@unturf
// https://www.permacomputer.com

/*
 * UncloseAI C Library using libcurl
 * OpenAI-compatible API client with streaming support
 * Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

#define MAX_ENDPOINTS 100
#define MAX_MODELS 100
#define MAX_URL_LEN 512
#define MAX_MODEL_LEN 256
#define MAX_CONTENT_LEN 1024

// Structure to hold response data
struct MemoryStruct {
    char *memory;
    size_t size;
};

// Structure to hold discovered model info
struct ModelInfo {
    char id[MAX_MODEL_LEN];
    char endpoint[MAX_URL_LEN];
    int max_tokens;
};

// UncloseAI Client structure
typedef struct {
    struct ModelInfo *models;
    int model_count;
    char tts_endpoints[MAX_ENDPOINTS][MAX_URL_LEN];
    int tts_count;
    int timeout;
} UncloseAIClient;

// Callback function type for streaming
typedef void (*StreamCallback)(const char *content, void *userdata);

// Structure for streaming context
struct StreamContext {
    StreamCallback callback;
    void *userdata;
    char buffer[8192];
    size_t buffer_pos;
};

/*************************************************************
 * LIBRARY API - Callback function to capture response data
 *************************************************************/
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct MemoryStruct *mem = (struct MemoryStruct *)userp;

    char *ptr = realloc(mem->memory, mem->size + realsize + 1);
    if(!ptr) {
        printf("Not enough memory (realloc returned NULL)\n");
        return 0;
    }

    mem->memory = ptr;
    memcpy(&(mem->memory[mem->size]), contents, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0;

    return realsize;
}

/*************************************************************
 * LIBRARY API - Extract content from SSE data chunk
 *************************************************************/
static void extract_sse_content(const char *data, char *content, size_t max_len) {
    // Look for "content":"..." pattern
    const char *content_marker = "\"content\":\"";
    const char *start = strstr(data, content_marker);
    if(!start) return;

    start += strlen(content_marker);
    const char *end = start;

    // Find closing quote, handling escaped quotes
    while(*end && *end != '"') {
        if(*end == '\\' && *(end+1)) {
            end += 2;
        } else {
            end++;
        }
    }

    size_t len = end - start;
    if(len > max_len - 1) len = max_len - 1;
    strncpy(content, start, len);
    content[len] = '\0';
}

/*************************************************************
 * LIBRARY API - Streaming callback for curl
 *************************************************************/
static size_t StreamWriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct StreamContext *ctx = (struct StreamContext *)userp;

    // Append to buffer
    char *data = (char *)contents;
    for(size_t i = 0; i < realsize; i++) {
        if(ctx->buffer_pos >= sizeof(ctx->buffer) - 1) {
            // Buffer full, skip
            continue;
        }

        ctx->buffer[ctx->buffer_pos++] = data[i];

        // Check for line ending
        if(data[i] == '\n' && ctx->buffer_pos >= 2 &&
           ctx->buffer[ctx->buffer_pos-2] == '\n') {
            ctx->buffer[ctx->buffer_pos] = '\0';

            // Process SSE line
            if(strncmp(ctx->buffer, "data: ", 6) == 0) {
                const char *json_data = ctx->buffer + 6;

                // Check for [DONE]
                if(strncmp(json_data, "[DONE]", 6) == 0) {
                    ctx->buffer_pos = 0;
                    break;
                }

                // Extract content
                char content[MAX_CONTENT_LEN];
                extract_sse_content(json_data, content, sizeof(content));

                if(strlen(content) > 0 && ctx->callback) {
                    ctx->callback(content, ctx->userdata);
                }
            }

            ctx->buffer_pos = 0;
        }
    }

    return realsize;
}

/*************************************************************
 * LIBRARY API - Extract model IDs from JSON
 *************************************************************/
static void extract_model_ids(UncloseAIClient *client, const char *json, const char *endpoint) {
    const char *search = json;
    const char *id_marker = "\"id\":\"";

    while((search = strstr(search, id_marker)) != NULL &&
          client->model_count < MAX_MODELS) {
        search += strlen(id_marker);
        const char *end = strchr(search, '"');
        if(end) {
            size_t len = end - search;
            if(len < MAX_MODEL_LEN) {
                // Skip modelperm-* entries
                if(strncmp(search, "modelperm-", 10) == 0) {
                    search = end + 1;
                    continue;
                }

                strncpy(client->models[client->model_count].id, search, len);
                client->models[client->model_count].id[len] = '\0';
                strncpy(client->models[client->model_count].endpoint, endpoint, MAX_URL_LEN-1);
                client->models[client->model_count].max_tokens = 8192;
                client->model_count++;
            }
        }
        search = end + 1;
    }
}

/*************************************************************
 * LIBRARY API - Initialize client and discover models
 *************************************************************/
UncloseAIClient* uncloseai_init(int timeout) {
    UncloseAIClient *client = (UncloseAIClient*)malloc(sizeof(UncloseAIClient));
    if(!client) return NULL;

    client->models = (struct ModelInfo*)malloc(MAX_MODELS * sizeof(struct ModelInfo));
    if(!client->models) {
        free(client);
        return NULL;
    }

    client->model_count = 0;
    client->tts_count = 0;
    client->timeout = timeout;

    printf("Initializing UncloseAI client...\n");

    // Discover chat/code models
    for(int i = 1; i < 10000; i++) {
        char var_name[32];
        snprintf(var_name, sizeof(var_name), "MODEL_ENDPOINT_%d", i);
        char *endpoint = getenv(var_name);
        if(!endpoint) break;

        printf("Endpoint %d: %s\n", i, endpoint);

        // Fetch models
        char url[MAX_URL_LEN];
        snprintf(url, sizeof(url), "%s/models", endpoint);

        CURL *curl = curl_easy_init();
        if(curl) {
            struct MemoryStruct chunk = {NULL, 0};
            chunk.memory = malloc(1);
            chunk.size = 0;

            curl_easy_setopt(curl, CURLOPT_URL, url);
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
            curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);

            CURLcode res = curl_easy_perform(curl);

            if(res == CURLE_OK && chunk.memory) {
                extract_model_ids(client, chunk.memory, endpoint);
            }

            free(chunk.memory);
            curl_easy_cleanup(curl);
        }
    }

    // Discover TTS endpoints
    for(int i = 1; i < 10000; i++) {
        char var_name[32];
        snprintf(var_name, sizeof(var_name), "TTS_ENDPOINT_%d", i);
        char *endpoint = getenv(var_name);
        if(!endpoint) break;
        strncpy(client->tts_endpoints[client->tts_count++], endpoint, MAX_URL_LEN-1);
    }

    printf("Discovered %d models, %d TTS endpoints\n\n", client->model_count, client->tts_count);

    return client;
}

/*************************************************************
 * LIBRARY API - Non-streaming chat completion
 *************************************************************/
int uncloseai_chat(UncloseAIClient *client, int model_idx, const char *prompt,
                   struct MemoryStruct *response) {
    if(model_idx >= client->model_count) return -1;

    char url[MAX_URL_LEN];
    char json[2048];

    snprintf(url, sizeof(url), "%s/chat/completions",
             client->models[model_idx].endpoint);
    snprintf(json, sizeof(json),
        "{\"model\":\"%s\","
        "\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}],"
        "\"stream\":false,"
        "\"temperature\":0.7,"
        "\"max_tokens\":100}",
        client->models[model_idx].id, prompt);

    CURL *curl = curl_easy_init();
    if(!curl) return -1;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)client->timeout);

    CURLcode res = curl_easy_perform(curl);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    return (res == CURLE_OK) ? 0 : -1;
}

/*************************************************************
 * LIBRARY API - Streaming chat completion
 *************************************************************/
int uncloseai_chat_stream(UncloseAIClient *client, int model_idx, const char *prompt,
                          StreamCallback callback, void *userdata) {
    if(model_idx >= client->model_count) return -1;

    char url[MAX_URL_LEN];
    char json[2048];

    snprintf(url, sizeof(url), "%s/chat/completions",
             client->models[model_idx].endpoint);
    snprintf(json, sizeof(json),
        "{\"model\":\"%s\","
        "\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}],"
        "\"stream\":true,"
        "\"temperature\":0.7,"
        "\"max_tokens\":500}",
        client->models[model_idx].id, prompt);

    CURL *curl = curl_easy_init();
    if(!curl) return -1;

    struct StreamContext ctx;
    ctx.callback = callback;
    ctx.userdata = userdata;
    ctx.buffer_pos = 0;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, StreamWriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&ctx);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)client->timeout);

    CURLcode res = curl_easy_perform(curl);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    return (res == CURLE_OK) ? 0 : -1;
}

/*************************************************************
 * LIBRARY API - Text-to-speech generation
 *************************************************************/
int uncloseai_tts(UncloseAIClient *client, const char *text, const char *voice,
                  const char *output_file) {
    if(client->tts_count == 0) return -1;

    char url[MAX_URL_LEN];
    char json[2048];

    snprintf(url, sizeof(url), "%s/audio/speech", client->tts_endpoints[0]);
    snprintf(json, sizeof(json),
        "{\"model\":\"tts-1\","
        "\"voice\":\"%s\","
        "\"input\":\"%s\"}",
        voice, text);

    CURL *curl = curl_easy_init();
    if(!curl) return -1;

    struct MemoryStruct chunk = {NULL, 0};
    chunk.memory = malloc(1);
    chunk.size = 0;

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)client->timeout);

    CURLcode res = curl_easy_perform(curl);

    int result = -1;
    if(res == CURLE_OK) {
        FILE *fp = fopen(output_file, "wb");
        if(fp) {
            fwrite(chunk.memory, 1, chunk.size, fp);
            fclose(fp);
            result = 0;
        }
    }

    free(chunk.memory);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    return result;
}

/*************************************************************
 * LIBRARY API - Free client resources
 *************************************************************/
void uncloseai_free(UncloseAIClient *client) {
    if(client) {
        if(client->models) free(client->models);
        free(client);
    }
}

/*************************************************************
 * DEMO PROGRAM - Shows library usage
 *************************************************************/

// Callback for streaming
void stream_callback(const char *content, void *userdata) {
    printf("%s", content);
    fflush(stdout);
}

int main(void) {
    printf("=== UncloseAI C Client (with Streaming) ===\n\n");

    curl_global_init(CURL_GLOBAL_ALL);

    // Initialize client
    UncloseAIClient *client = uncloseai_init(30);
    if(!client || client->model_count == 0) {
        printf("ERROR: No models discovered\n");
        curl_global_cleanup();
        return 1;
    }

    // Non-streaming chat example
    printf("=== Non-Streaming Chat ===\n");
    printf("Model: %s\n", client->models[0].id);

    struct MemoryStruct response = {NULL, 0};
    response.memory = malloc(1);
    response.size = 0;

    if(uncloseai_chat(client, 0, "Explain quantum computing in one sentence",
                      &response) == 0) {
        printf("Response: (%zu bytes received)\n", response.size);
    }
    free(response.memory);
    printf("\n");

    // Streaming chat example
    int model_idx = (client->model_count >= 2) ? 1 : 0;
    printf("=== Streaming Chat ===\n");
    printf("Model: %s\n", client->models[model_idx].id);
    printf("Response: ");

    uncloseai_chat_stream(client, model_idx,
                          "Write a hello world program in C",
                          stream_callback, NULL);
    printf("\n\n");

    // TTS example
    if(client->tts_count > 0) {
        printf("=== TTS Speech Generation ===\n");
        printf("Model: tts-1\n");

        if(uncloseai_tts(client, "Hello from UncloseAI C client!",
                         "alloy", "/tmp/speech.mp3") == 0) {
            printf("Audio saved to /tmp/speech.mp3\n");
        } else {
            printf("TTS failed\n");
        }
    }

    printf("\n=== Examples Complete ===\n");

    uncloseai_free(client);
    curl_global_cleanup();
    return 0;
}
