// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-or-later

/**
 * @file partition_monitor.cpp
 * @brief Partition-map kind classifier, JSON serializer, and flash walk.
 *
 * The classifier and serializer are pure (host-tested); the walk uses
 * esp_partition / esp_ota_ops on ESP32 and is a no-op on host builds. No server
 * dependency lives here - the route is in partition_monitor_routes.cpp.
 */

#include "services/partition_monitor/partition_monitor.h"

#if DETWS_ENABLE_PARTITION_MONITOR

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

// esp_partition type/subtype constants (mirrors esp_partition_type_t/subtype_t so
// the classifier stays pure and host-testable without the IDF headers).
const char *detws_partition_kind(uint8_t type, uint8_t subtype)
{
    if (type == 0) // ESP_PARTITION_TYPE_APP
    {
        if (subtype == 0x00)
            return "factory";
        if (subtype >= 0x10 && subtype <= 0x1F)
            return "ota";
        if (subtype == 0x20)
            return "test";
        return "app";
    }
    switch (subtype) // ESP_PARTITION_TYPE_DATA
    {
    case 0x00:
        return "otadata";
    case 0x01:
        return "phy";
    case 0x02:
        return "nvs";
    case 0x03:
        return "coredump";
    case 0x04:
        return "nvs_keys";
    case 0x81:
        return "fat";
    case 0x82:
        return "spiffs";
    case 0x83:
        return "littlefs";
    default:
        return "data";
    }
}

static int json_append(char *out, size_t cap, size_t *pos, const char *fmt, ...)
{
    if (*pos >= cap)
        return -1;
    va_list ap;
    va_start(ap, fmt);
    int w = vsnprintf(out + *pos, cap - *pos, fmt, ap);
    va_end(ap);
    if (w < 0 || (size_t)w >= cap - *pos)
        return -1;
    *pos += (size_t)w;
    return 0;
}

int detws_partition_json(const DetwsPartitionInfo *parts, uint8_t count, char *out, size_t cap)
{
    if (!out || cap == 0)
        return 0;
    out[0] = '\0';
    if (!parts)
        return 0;
    size_t pos = 0;
    if (json_append(out, cap, &pos, "{\"partitions\":[") != 0)
        return 0;
    for (uint8_t i = 0; i < count; i++)
    {
        const DetwsPartitionInfo *p = &parts[i];
        if (json_append(out, cap, &pos,
                        "%s{\"label\":\"%s\",\"kind\":\"%s\",\"type\":%u,\"subtype\":%u,\"addr\":%u,\"size\":%u,"
                        "\"running\":%s}",
                        i ? "," : "", p->label, detws_partition_kind(p->type, p->subtype), (unsigned)p->type,
                        (unsigned)p->subtype, (unsigned)p->address, (unsigned)p->size,
                        p->running ? "true" : "false") != 0)
            return 0;
    }
    if (json_append(out, cap, &pos, "]}") != 0)
        return 0;
    return (int)pos;
}

#ifdef ARDUINO

#include <esp_ota_ops.h>
#include <esp_partition.h>

uint8_t detws_partition_collect(DetwsPartitionInfo *out, uint8_t max)
{
    if (!out || max == 0)
        return 0;
    const esp_partition_t *running = esp_ota_get_running_partition();
    uint8_t n = 0;
    esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
    for (; it != NULL && n < max; it = esp_partition_next(it))
    {
        const esp_partition_t *p = esp_partition_get(it);
        DetwsPartitionInfo *d = &out[n++];
        strncpy(d->label, p->label, sizeof(d->label) - 1);
        d->label[sizeof(d->label) - 1] = '\0';
        d->type = (uint8_t)p->type;
        d->subtype = (uint8_t)p->subtype;
        d->address = p->address;
        d->size = p->size;
        d->running = (running != NULL && p->address == running->address);
    }
    esp_partition_iterator_release(it);
    return n;
}

#else // host build - no flash

uint8_t detws_partition_collect(DetwsPartitionInfo *, uint8_t)
{
    return 0;
}

#endif // ARDUINO

#endif // DETWS_ENABLE_PARTITION_MONITOR
