#define _POSIX_C_SOURCE 200809L

#include "prompt_template.h"
#include "cjson/cJSON.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

static char* dup_range(const char* s, size_t n) {
    char* out = (char*)malloc(n + 1U);
    if (!out) return NULL;
    if (n > 0) memcpy(out, s, n);
    out[n] = '\0';
    return out;
}

static char* ltrim_inplace(char* s) {
    if (!s) return s;
    while (*s && isspace((unsigned char)*s)) s++;
    return s;
}

static void rtrim_inplace(char* s) {
    if (!s) return;
    size_t n = strlen(s);
    while (n > 0 && isspace((unsigned char)s[n - 1])) {
        s[n - 1] = '\0';
        n--;
    }
}

static int append_text(char** buf, size_t* cap, size_t* used, const char* s) {
    if (!buf || !cap || !used || !s) return -1;
    size_t n = strlen(s);
    if (*used + n + 1U > *cap) {
        size_t next = *cap;
        while (*used + n + 1U > next) {
            next = (next == 0U) ? 256U : (next * 2U);
        }
        char* grown = (char*)realloc(*buf, next);
        if (!grown) return -1;
        *buf = grown;
        *cap = next;
    }
    memcpy(*buf + *used, s, n);
    *used += n;
    (*buf)[*used] = '\0';
    return 0;
}

static const char* map_variable_tool_name(const char* var_name) {
    if (!var_name || var_name[0] == '\0') return NULL;

    if (strcmp(var_name, "admin_profile") == 0) return "nostr_admin_profile";
    if (strcmp(var_name, "admin_notes") == 0) return "nostr_admin_notes";
    if (strcmp(var_name, "admin_relays") == 0) return "nostr_admin_relays";
    if (strcmp(var_name, "adopted_skills") == 0) return "adopted_skills";
    if (strcmp(var_name, "triggering_event") == 0) return "trigger_event";

    return var_name;
}

char* prompt_template_resolve_inline_variables(const char* tpl, tools_context_t* tools_ctx) {
    if (!tpl) return strdup("");

    size_t cap = strlen(tpl) + 64U;
    size_t used = 0U;
    char* out = (char*)malloc(cap);
    if (!out) return NULL;
    out[0] = '\0';

    const char* p = tpl;
    while (*p) {
        const char* open = strstr(p, "{{");
        if (!open) {
            if (append_text(&out, &cap, &used, p) != 0) {
                free(out);
                return NULL;
            }
            break;
        }

        if (open > p) {
            char* prefix = dup_range(p, (size_t)(open - p));
            if (!prefix) {
                free(out);
                return NULL;
            }
            int rc = append_text(&out, &cap, &used, prefix);
            free(prefix);
            if (rc != 0) {
                free(out);
                return NULL;
            }
        }

        const char* close = strstr(open + 2, "}}");
        if (!close) {
            if (append_text(&out, &cap, &used, open) != 0) {
                free(out);
                return NULL;
            }
            break;
        }

        char* var = dup_range(open + 2, (size_t)(close - (open + 2)));
        if (!var) {
            free(out);
            return NULL;
        }
        char* var_trim = ltrim_inplace(var);
        rtrim_inplace(var_trim);

        const char* replacement = "";
        char* replacement_owned = NULL;
        char* call_name_owned = NULL;
        char* call_args_owned = NULL;
        const char* lookup_name = var_trim;
        const char* call_args_json = "{}";

        size_t var_len = strlen(var_trim);
        if (var_len > 0 && var_trim[var_len - 1] == ')') {
            char* open_paren = strchr(var_trim, '(');
            if (open_paren && open_paren < (var_trim + var_len - 1)) {
                call_name_owned = dup_range(var_trim, (size_t)(open_paren - var_trim));
                if (!call_name_owned) {
                    free(var);
                    free(out);
                    return NULL;
                }
                char* call_name_trim = ltrim_inplace(call_name_owned);
                rtrim_inplace(call_name_trim);

                char* raw_args = dup_range(open_paren + 1,
                                           (size_t)((var_trim + var_len - 1) - (open_paren + 1)));
                if (!raw_args) {
                    free(call_name_owned);
                    free(var);
                    free(out);
                    return NULL;
                }
                char* raw_args_trim = ltrim_inplace(raw_args);
                rtrim_inplace(raw_args_trim);

                if (call_name_trim[0] != '\0') {
                    lookup_name = call_name_trim;
                    if (raw_args_trim[0] != '\0') {
                        cJSON* parsed_args = cJSON_Parse(raw_args_trim);
                        if (parsed_args) {
                            call_args_owned = strdup(raw_args_trim);
                            cJSON_Delete(parsed_args);
                        }
                    }
                }

                free(raw_args);
            }
        }

        if (call_args_owned) {
            call_args_json = call_args_owned;
        }

        if (strcmp(lookup_name, "message") == 0) {
            replacement = (tools_ctx && tools_ctx->template_current_user_message)
                              ? tools_ctx->template_current_user_message
                              : "";
        } else if (tools_ctx) {
            const char* tool_name = map_variable_tool_name(lookup_name);
            if (tool_name && tool_name[0] != '\0') {
                char* tool_result = tools_execute(tools_ctx, tool_name, call_args_json);
                if (tool_result) {
                    cJSON* root = cJSON_Parse(tool_result);
                    if (root && cJSON_IsObject(root)) {
                        cJSON* success = cJSON_GetObjectItemCaseSensitive(root, "success");
                        if (!success || !cJSON_IsBool(success) || cJSON_IsTrue(success)) {
                            cJSON* content = cJSON_GetObjectItemCaseSensitive(root, "content");
                            if (content && cJSON_IsString(content) && content->valuestring) {
                                replacement_owned = strdup(content->valuestring);
                            } else if (content) {
                                replacement_owned = cJSON_PrintUnformatted(content);
                            }
                        }
                    }
                    cJSON_Delete(root);
                    free(tool_result);
                }
            }

            if (!replacement_owned && tools_ctx->template_skill_lookup && lookup_name[0] != '\0') {
                const char* skill_content = tools_ctx->template_skill_lookup(
                    tools_ctx->template_skill_lookup_user_data,
                    lookup_name);
                if (skill_content && skill_content[0] != '\0') {
                    replacement_owned = strdup(skill_content);
                }
            }

            replacement = replacement_owned ? replacement_owned : "";
        }

        if (append_text(&out, &cap, &used, replacement) != 0) {
            free(call_args_owned);
            free(call_name_owned);
            free(replacement_owned);
            free(var);
            free(out);
            return NULL;
        }

        free(call_args_owned);
        free(call_name_owned);
        free(replacement_owned);
        free(var);
        p = close + 2;
    }

    return out;
}
