#define _POSIX_C_SOURCE 200809L

#include "json_to_markdown.h"
#include "cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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 void print_indent(int indent_level, char** buf, size_t* cap, size_t* used) {
    for (int i = 0; i < indent_level; i++) {
        append_text(buf, cap, used, "  ");
    }
}

static void render_json_node(cJSON* node, int indent_level, int is_list_item, char** buf, size_t* cap, size_t* used, int max_array_items) {
    if (!node) return;

    if (cJSON_IsString(node)) {
        if (is_list_item) {
            print_indent(indent_level, buf, cap, used);
            append_text(buf, cap, used, "- ");
        }
        append_text(buf, cap, used, node->valuestring ? node->valuestring : "");
        append_text(buf, cap, used, "\n");
    } else if (cJSON_IsNumber(node)) {
        if (is_list_item) {
            print_indent(indent_level, buf, cap, used);
            append_text(buf, cap, used, "- ");
        }
        char num_str[64];
        if (node->valuedouble == (double)node->valueint) {
            snprintf(num_str, sizeof(num_str), "%d", node->valueint);
        } else {
            snprintf(num_str, sizeof(num_str), "%f", node->valuedouble);
        }
        append_text(buf, cap, used, num_str);
        append_text(buf, cap, used, "\n");
    } else if (cJSON_IsBool(node)) {
        if (is_list_item) {
            print_indent(indent_level, buf, cap, used);
            append_text(buf, cap, used, "- ");
        }
        append_text(buf, cap, used, cJSON_IsTrue(node) ? "true\n" : "false\n");
    } else if (cJSON_IsNull(node)) {
        if (is_list_item) {
            print_indent(indent_level, buf, cap, used);
            append_text(buf, cap, used, "- null\n");
        }
    } else if (cJSON_IsObject(node)) {
        cJSON* child = node->child;
        int first = 1;
        while (child) {
            if (is_list_item && first) {
                print_indent(indent_level, buf, cap, used);
                append_text(buf, cap, used, "- **");
                first = 0;
            } else {
                print_indent(indent_level + (is_list_item ? 1 : 0), buf, cap, used);
                append_text(buf, cap, used, "- **");
            }
            append_text(buf, cap, used, child->string ? child->string : "unknown");
            append_text(buf, cap, used, "**: ");
            
            if (cJSON_IsObject(child) || cJSON_IsArray(child)) {
                append_text(buf, cap, used, "\n");
                render_json_node(child, indent_level + (is_list_item ? 2 : 1), 0, buf, cap, used, max_array_items);
            } else {
                render_json_node(child, 0, 0, buf, cap, used, max_array_items);
            }
            child = child->next;
        }
    } else if (cJSON_IsArray(node)) {
        int count = cJSON_GetArraySize(node);
        int limit = (max_array_items > 0 && count > max_array_items) ? max_array_items : count;
        for (int i = 0; i < limit; i++) {
            cJSON* item = cJSON_GetArrayItem(node, i);
            render_json_node(item, indent_level, 1, buf, cap, used, max_array_items);
        }
        if (count > limit) {
            print_indent(indent_level, buf, cap, used);
            char trunc_msg[64];
            snprintf(trunc_msg, sizeof(trunc_msg), "- *(... and %d more items)*\n", count - limit);
            append_text(buf, cap, used, trunc_msg);
        }
    }
}

char* json_to_markdown(const char* json_string, int max_array_items) {
    if (!json_string) return NULL;

    cJSON* root = cJSON_Parse(json_string);
    if (!root) {
        // If it's not valid JSON, just return a copy of the string
        return strdup(json_string);
    }

    // If it's a plain string, return it without markdown formatting
    if (cJSON_IsString(root)) {
        char* out = strdup(root->valuestring ? root->valuestring : "");
        cJSON_Delete(root);
        return out;
    }

    size_t cap = 1024;
    size_t used = 0;
    char* buf = (char*)malloc(cap);
    if (!buf) {
        cJSON_Delete(root);
        return NULL;
    }
    buf[0] = '\0';

    render_json_node(root, 0, 0, &buf, &cap, &used, max_array_items);

    cJSON_Delete(root);

    // Trim trailing newline if present
    if (used > 0 && buf[used - 1] == '\n') {
        buf[used - 1] = '\0';
    }

    return buf;
}
