/**
 * @file    ws_method.cpp
 * @brief   WebSocket 命令解析与信号 CRUD 操作
 * @details 接收 JSON 命令（{type, saddr, signal_type, curd, signal_data}），
 *          通过 g_ws_sig_methods 函数表分发到对应的信号增删改查函数。
 *          支持 cmd/get_cmds/get_file/set_file 等管理命令。
 */
#include "web_server.h"
#include "myLog.h"
#include "myDatacenter.h"
#include "myCmd.h"
#include "myFunc.h"
#include "myMongoose.h"
#include "cJSON.h"

#include <string>
#include <vector>
#include <map>
#include <cstring>
#include <unistd.h>

/* ---- 前向声明（来自 web_server.cpp） ---- */
extern std::string ws_cmd_exec(const char *cmd_line);

/* ---- Per-Connection Session ---- */

typedef struct
{
    std::string saddr;
    std::string desc;
    uint8_t     data_type;
    uint8_t     ctrl_type;
    std::vector<void *> vec_p_data;
    std::string last_val;
} stru_ws_sig;

typedef struct
{
    std::vector<stru_ws_sig> signals[WS_SIG_MAX];
} stru_ws_session;

static std::map<unsigned long, stru_ws_session> g_ws_sessions;
static pthread_mutex_t g_ws_session_mutex = PTHREAD_MUTEX_INITIALIZER;

/* WebSocket 收发包日志开关（默认关闭，通过 web_log on/off 控制） */
static int g_ws_rx_log = 0;

/* ---- 前向声明 ---- */
static int  ws_make_sig_json(stru_ws_session *s, cJSON *root, enum_ws_sig_type st, bool *has_change);
static void ws_sig_add(stru_ws_session *s, enum_ws_sig_type st, const char *saddr);
static void ws_sig_del(stru_ws_session *s, enum_ws_sig_type st, const char *saddr);
static int  ws_sig_set(stru_ws_session *s, enum_ws_sig_type st, const char *saddr,
                       uint8_t setting_zone, const char *val);

/* ---- 函数表: 信号类型 → 名称 ---- */
static const char *g_ws_sig_names[WS_SIG_MAX] =
{
    "out", "yk", "ao", "param"
};

/* ================================================================
 * 内部工具
 * ================================================================ */

static int ws_sig_find_idx(const std::vector<stru_ws_sig> &vec, const char *saddr)
{
    for (size_t i = 0; i < vec.size(); i++)
    {
        if (vec[i].saddr == saddr)
        {
            return (int)i;
        }
    }

    return -1;
}

static std::string ws_get_val_str(const void *p_data, uint8_t data_type)
{
    char buf[128] = {0};
    dc_get_signal_val(p_data, data_type, buf, sizeof(buf));

    return std::string(buf);
}

/* ================================================================
 * 信号增删改 — out/in/yk/ao/param 分 5 种路径
 * ================================================================ */

static void ws_sig_add_out(stru_ws_session *s, const char *saddr)
{
    std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_OUT];
    void *p_data = NULL;

    if (ws_sig_find_idx(vec, saddr) >= 0)
    {
        return;
    }

    stru_ws_sig sig;
    sig.saddr   = saddr;
    sig.data_type = 0;

    char desc[128] = {0};

    if (0 != dc_get_out_signal_info(saddr, desc, sizeof(desc), &sig.data_type, &p_data))
    {
        LOG_E("dc_get_out_signal_info failed: %s", saddr);

        return;
    }

    sig.desc = desc;
    sig.vec_p_data.push_back(p_data);
    vec.push_back(sig);
}

static void ws_sig_add_yk(stru_ws_session *s, const char *saddr)
{
    std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_YK];
    void *p_data = NULL;

    if (ws_sig_find_idx(vec, saddr) >= 0)
    {
        return;
    }

    stru_ws_sig sig;
    sig.saddr   = saddr;

    char desc[128] = {0};

    if (0 != dc_get_yk_signal_info(saddr, desc, sizeof(desc), &sig.data_type, &sig.ctrl_type, &p_data))
    {
        LOG_E("dc_get_yk_signal_info failed: %s", saddr);

        return;
    }

    sig.desc = desc;
    sig.vec_p_data.push_back(p_data);
    vec.push_back(sig);
}

static void ws_sig_add_ao(stru_ws_session *s, const char *saddr)
{
    std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_AO];
    void *p_data = NULL;

    if (ws_sig_find_idx(vec, saddr) >= 0)
    {
        return;
    }

    stru_ws_sig sig;
    sig.saddr   = saddr;

    char desc[128] = {0};

    if (0 != dc_get_ao_signal_info(saddr, desc, sizeof(desc), &sig.data_type, NULL, &sig.ctrl_type, &p_data))
    {
        LOG_E("dc_get_ao_signal_info failed: %s", saddr);

        return;
    }

    sig.desc = desc;
    sig.vec_p_data.push_back(p_data);
    vec.push_back(sig);
}

static void ws_sig_add_param(stru_ws_session *s, const char *saddr)
{
    std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_PARAM];
    void *p_data_arr[16] = {NULL};
    int num_zones = 0;

    if (ws_sig_find_idx(vec, saddr) >= 0)
    {
        return;
    }

    stru_ws_sig sig;
    sig.saddr   = saddr;

    char desc[128] = {0};

    if (0 != dc_get_param_signal_info(saddr, desc, sizeof(desc), &sig.data_type, NULL, &sig.ctrl_type, p_data_arr, &num_zones))
    {
        LOG_E("dc_get_param_signal_info failed: %s", saddr);

        return;
    }

    sig.desc = desc;

    for (int i = 0; i < num_zones; i++)
    {
        sig.vec_p_data.push_back(p_data_arr[i]);
    }

    vec.push_back(sig);
}

static void ws_sig_add(stru_ws_session *s, enum_ws_sig_type st, const char *saddr)
{
    switch (st)
    {
        case WS_SIG_OUT:   ws_sig_add_out(s, saddr);   break;
        case WS_SIG_YK:    ws_sig_add_yk(s, saddr);    break;
        case WS_SIG_AO:    ws_sig_add_ao(s, saddr);    break;
        case WS_SIG_PARAM: ws_sig_add_param(s, saddr); break;
        default: break;
    }
}

static void ws_sig_del(stru_ws_session *s, enum_ws_sig_type st, const char *saddr)
{
    std::vector<stru_ws_sig> &vec = s->signals[st];

    for (size_t i = 0; i < vec.size(); i++)
    {
        if (vec[i].saddr == saddr)
        {
            vec.erase(vec.begin() + i);

            return;
        }
    }
}

static int ws_sig_set(stru_ws_session *s, enum_ws_sig_type st, const char *saddr,
                      uint8_t setting_zone, const char *val)
{
    std::vector<stru_ws_sig> &vec = s->signals[st];
    uint8_t data[128] = {0};

    for (size_t i = 0; i < vec.size(); i++)
    {
        if (vec[i].saddr != saddr)
        {
            continue;
        }

        dc_set_val_from_str(data, vec[i].data_type, val);

        switch (st)
        {
            case WS_SIG_OUT:
                return dc_set_out_signal_val(saddr, data, MODULE_WEB_SERVER);

            case WS_SIG_YK:
            {
                dc_ctrl_t ctrl;
                ctrl.type      = vec[i].ctrl_type;
                ctrl.step      = DC_STEP_READY;
                ctrl.data_type = vec[i].data_type;
                ctrl.p_data    = dc_create_data(vec[i].data_type);

                int rc = dc_signal_yk_set_status(saddr, DC_STEP_DIRECT, &ctrl, data, MODULE_WEB_SERVER);
                dc_destroy_data(ctrl.p_data, vec[i].data_type);

                return rc;
            }

            case WS_SIG_AO:
            {
                dc_ctrl_t ctrl;
                ctrl.type      = vec[i].ctrl_type;
                ctrl.step      = DC_STEP_READY;
                ctrl.data_type = vec[i].data_type;
                ctrl.p_data    = dc_create_data(vec[i].data_type);

                int rc = dc_signal_ao_set_val(saddr, DC_STEP_DIRECT, &ctrl, data, MODULE_WEB_SERVER);
                dc_destroy_data(ctrl.p_data, vec[i].data_type);

                return rc;
            }

            case WS_SIG_PARAM:
            {
                dc_ctrl_t ctrl;
                ctrl.type      = vec[i].ctrl_type;
                ctrl.step      = DC_STEP_READY;
                ctrl.data_type = vec[i].data_type;
                ctrl.p_data    = dc_create_data(vec[i].data_type);

                int rc = dc_signal_param_set_val(saddr, DC_STEP_DIRECT, &ctrl, setting_zone, data, MODULE_WEB_SERVER);
                dc_destroy_data(ctrl.p_data, vec[i].data_type);

                return rc;
            }

            default:
                break;
        }
    }

    LOG_E("signal not found in session: %s", saddr);

    return -1;
}

/* ================================================================
 * JSON 构建 — 按信号类型有不同字段
 * ================================================================ */

static int ws_make_sig_json(stru_ws_session *s, cJSON *root, enum_ws_sig_type st, bool *has_change)
{
    const std::vector<stru_ws_sig> &vec = s->signals[st];

    if (vec.empty())
    {
        return 0;
    }

    cJSON *arr = cJSON_CreateArray();
    cJSON_AddItemToObject(root, g_ws_sig_names[st], arr);

    for (size_t i = 0; i < vec.size(); i++)
    {
        const stru_ws_sig *sig = &vec[i];
        cJSON *item = cJSON_CreateObject();

        cJSON_AddStringToObject(item, "saddr", sig->saddr.c_str());
        cJSON_AddStringToObject(item, "desc",  sig->desc.c_str());
        cJSON_AddStringToObject(item, "type",  dc_get_type_name(sig->data_type));

        /* yk/ao/param: 控制类型 */
        if (st == WS_SIG_YK || st == WS_SIG_AO || st == WS_SIG_PARAM)
        {
            cJSON_AddNumberToObject(item, "ctrl_type", sig->ctrl_type);
        }

        /* param: 多定值区 */
        if (st == WS_SIG_PARAM)
        {
            cJSON *zone_arr = cJSON_CreateArray();
            cJSON_AddItemToObject(item, "setting_zone_list", zone_arr);

            for (size_t j = 0; j < sig->vec_p_data.size(); j++)
            {
                cJSON *zone_item = cJSON_CreateObject();

                std::string id  = std::to_string(j);
                std::string val = ws_get_val_str(sig->vec_p_data[j], sig->data_type);

                cJSON_AddStringToObject(zone_item, "id",  id.c_str());
                cJSON_AddStringToObject(zone_item, "val", val.c_str());
                cJSON_AddItemToArray(zone_arr, zone_item);
            }

            /* 组合值用于变化检测 */
            std::string combined;
            for (size_t j = 0; j < sig->vec_p_data.size(); j++)
            {
                combined += ws_get_val_str(sig->vec_p_data[j], sig->data_type);
                combined += ",";
            }

            if (combined != sig->last_val)
            {
                /* 注意: last_val 是 const 方法访问，这里需要 const_cast */
                const_cast<stru_ws_sig *>(sig)->last_val = combined;
                *has_change = true;
            }
        }
        else
        {
            /* out/in/yk/ao: 单值 */
            std::string val = ws_get_val_str(sig->vec_p_data[0], sig->data_type);

            cJSON_AddStringToObject(item, "val", val.c_str());

            if (val != sig->last_val)
            {
                const_cast<stru_ws_sig *>(sig)->last_val = val;
                *has_change = true;
            }
        }

        cJSON_AddItemToArray(arr, item);
    }

    return 0;
}

/* ================================================================
 * 增量推送引擎（在 EV_TIMER2 中调用）
 * ================================================================ */

void ws_push_task(void)
{
    pthread_mutex_lock(&g_ws_session_mutex);

    for (auto &pair : g_ws_sessions)
    {
        unsigned long conn_id = pair.first;
        stru_ws_session &sess = pair.second;

        bool has_any = false;
        for (int st = 0; st < WS_SIG_MAX; st++)
        {
            if (!sess.signals[st].empty())
            {
                has_any = true;
                break;
            }
        }

        if (!has_any)
        {
            continue;
        }

        cJSON *root = cJSON_CreateObject();

        bool has_change = false;

        for (int st = 0; st < WS_SIG_MAX; st++)
        {
            ws_make_sig_json(&sess, root, (enum_ws_sig_type)st, &has_change);
        }

        if (!has_change)
        {
            cJSON_Delete(root);
            continue;
        }

        char *p_tx = cJSON_PrintUnformatted(root);
        cJSON_Delete(root);

        if (p_tx)
        {
            ws_send_one(conn_id, p_tx, strlen(p_tx));
            free(p_tx);
        }
    }

    pthread_mutex_unlock(&g_ws_session_mutex);
}

/* ================================================================
 * WebSocket JSON 命令分发
 * ================================================================ */

void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
{
    if (!p_rx || rx_len == 0)
    {
        return;
    }

    {
        char buf[256] = {0};
        int copy_len = (int)rx_len < 255 ? (int)rx_len : 255;
        memcpy(buf, p_rx, (size_t)copy_len);
        buf[copy_len] = '\0';

        if (g_ws_rx_log)
        {
            LOG_I("WS recv %uB: %s", rx_len, buf);
        }
    }

    cJSON *root = cJSON_Parse(p_rx);

    if (!root)
    {
        LOG_E("ws_recv parse failed: %.200s", p_rx);

        return;
    }

    /* ---- type=cmd: 远程命令执行 ---- */
    cJSON *jt = cJSON_GetObjectItem(root, "type");

    if (jt && cJSON_IsString(jt) && jt->valuestring)
    {
        if (0 == strcmp(jt->valuestring, "cmd"))
        {
            cJSON *jd = cJSON_GetObjectItem(root, "data");

            if (jd && cJSON_IsString(jd) && jd->valuestring)
            {
                /* 提取模块名存异步通道 */
                char mod[64] = {0};
                const char *pp = jd->valuestring;
                int mi = 0;

                while (*pp && *pp != ' ' && *pp != '\t' && mi < 63)
                {
                    mod[mi++] = *pp++;
                }

                ws_set_async_cmd_conn(mod, c->id);

                std::string out = ws_cmd_exec(jd->valuestring);
                ws_send_binary(c->id, out.data(), out.size());

                /* datacenter 命令特殊处理：导出 XML + 返回信号元数据 */
                if (0 == strncmp(jd->valuestring, "datacenter", 10))
                {
                    const char *sub = jd->valuestring + 10;

                    while (*sub == ' ' || *sub == '\t')
                    {
                        sub++;
                    }

                    /* 构建信号元数据 JSON */
                    const char *dc_types[4];
                    int dc_type_count = 0;

                    if (0 == strcmp(sub, "all") || 0 == strncmp(sub, "all ", 4))
                    {
                        dc_types[dc_type_count++] = "out";
                        dc_types[dc_type_count++] = "yk";
                        dc_types[dc_type_count++] = "ao";
                        dc_types[dc_type_count++] = "param";
                    }
                    else if (0 == strcmp(sub, "out") || 0 == strncmp(sub, "out ", 4))
                    {
                        dc_types[dc_type_count++] = "out";
                    }
                    else if (0 == strcmp(sub, "yk")  || 0 == strncmp(sub, "yk ", 3))
                    {
                        dc_types[dc_type_count++] = "yk";
                    }
                    else if (0 == strcmp(sub, "ao")  || 0 == strncmp(sub, "ao ", 3))
                    {
                        dc_types[dc_type_count++] = "ao";
                    }
                    else if (0 == strcmp(sub, "param") || 0 == strncmp(sub, "param ", 6))
                    {
                        dc_types[dc_type_count++] = "param";
                    }
                    else
                    {
                        /* 默认返回全部 */
                        dc_types[dc_type_count++] = "out";
                        dc_types[dc_type_count++] = "yk";
                        dc_types[dc_type_count++] = "ao";
                        dc_types[dc_type_count++] = "param";
                    }

                    /* 构建 dc_data JSON */
                    cJSON *dc_resp = cJSON_CreateObject();
                    cJSON_AddStringToObject(dc_resp, "type", "dc_data");

                    for (int si = 0; si < dc_type_count; si++)
                    {
                        cJSON *arr = cJSON_CreateArray();
                        uint32_t count = dc_get_signal_count(dc_types[si]);

                        for (uint32_t j = 0; j < count; j++)
                        {
                            char saddr[128] = {0};
                            char desc[256] = {0};
                            char dts[32] = {0};
                            uint8_t ctrl_type = 0;
                            char link_str[256] = {0};

                            if (0 != dc_get_signal_info_by_id(dc_types[si], j,
                                                              saddr, sizeof(saddr),
                                                              desc, sizeof(desc),
                                                              dts, sizeof(dts),
                                                              &ctrl_type,
                                                              link_str, sizeof(link_str)))
                            {
                                continue;
                            }

                            cJSON *item = cJSON_CreateObject();
                            cJSON_AddNumberToObject(item, "id", j);
                            cJSON_AddStringToObject(item, "saddr", saddr);
                            cJSON_AddStringToObject(item, "desc", desc);
                            cJSON_AddStringToObject(item, "type", dts);
                            cJSON_AddNumberToObject(item, "ctrl_type", ctrl_type);
                            cJSON_AddStringToObject(item, "link_saddrs", link_str);
                            cJSON_AddItemToArray(arr, item);
                        }

                        cJSON_AddItemToObject(dc_resp, dc_types[si], arr);
                    }

                    char *dc_tx = cJSON_PrintUnformatted(dc_resp);

                    if (dc_tx)
                    {
                        ws_send_one(c->id, dc_tx, strlen(dc_tx));
                        free(dc_tx);
                    }

                    cJSON_Delete(dc_resp);
                }
            }

            cJSON_Delete(root);

            return;
        }

        /* ---- type=get_cmds: 获取命令列表 ---- */
        if (0 == strcmp(jt->valuestring, "get_cmds"))
        {
            unsigned int count = 0;
            stru_cmd *cmds = cmd_manager_get_commands(&count);

            cJSON *resp = cJSON_CreateObject();
            cJSON_AddStringToObject(resp, "type", "cmd_list");
            cJSON *arr = cJSON_CreateArray();

            for (unsigned int i = 0; i < count; i++)
            {
                cJSON *item = cJSON_CreateObject();
                cJSON_AddStringToObject(item, "name", cmds[i].name);
                cJSON_AddStringToObject(item, "desc", cmds[i].desc);
                cJSON_AddItemToArray(arr, item);
            }

            cJSON_AddItemToObject(resp, "cmds", arr);

            char *p_tx = cJSON_PrintUnformatted(resp);

            if (p_tx)
            {
                ws_send_one(c->id, p_tx, strlen(p_tx));
                free(p_tx);
            }

            cJSON_Delete(resp);
            cJSON_Delete(root);

            return;
        }

        /* ---- type=get_file: 读取配置文件 ---- */
        if (0 == strcmp(jt->valuestring, "get_file"))
        {
            cJSON *jpath = cJSON_GetObjectItem(root, "path");
            cJSON *resp = cJSON_CreateObject();
            cJSON_AddStringToObject(resp, "type", "file_content");

            if (jpath && cJSON_IsString(jpath) && jpath->valuestring)
            {
                char resolved[512] = {0};

                if (jpath->valuestring[0] == '/')
                {
                    strncpy(resolved, jpath->valuestring, sizeof(resolved) - 1);
                }
                else
                {
                    char proc_dir[256] = {0};

                    if (0 == func_proc_self_dir(proc_dir, sizeof(proc_dir)))
                    {
                        snprintf(resolved, sizeof(resolved), "%s/%s", proc_dir, jpath->valuestring);
                    }
                    else
                    {
                        strncpy(resolved, jpath->valuestring, sizeof(resolved) - 1);
                    }
                }

                cJSON_AddStringToObject(resp, "path", jpath->valuestring);

                FILE *fp = fopen(resolved, "rb");

                if (fp)
                {
                    fseek(fp, 0, SEEK_END);
                    long fsize = ftell(fp);
                    fseek(fp, 0, SEEK_SET);

                    if (fsize > 0 && fsize < 1024 * 1024)
                    {
                        char *buf = (char *)malloc((size_t)fsize + 1);

                        if (buf)
                        {
                            size_t n = fread(buf, 1, (size_t)fsize, fp);
                            buf[n] = '\0';
                            cJSON_AddStringToObject(resp, "content", buf);
                            free(buf);
                        }
                    }

                    fclose(fp);
                }
            }

            char *p_tx = cJSON_PrintUnformatted(resp);

            if (p_tx)
            {
                ws_send_one(c->id, p_tx, strlen(p_tx));
                free(p_tx);
            }

            cJSON_Delete(resp);
            cJSON_Delete(root);

            return;
        }

        /* ---- type=set_file: 保存配置文件 ---- */
        if (0 == strcmp(jt->valuestring, "set_file"))
        {
            cJSON *jpath    = cJSON_GetObjectItem(root, "path");
            cJSON *jcontent = cJSON_GetObjectItem(root, "content");
            cJSON *resp = cJSON_CreateObject();
            cJSON_AddStringToObject(resp, "type", "file_saved");

            if (jpath && cJSON_IsString(jpath) && jcontent && cJSON_IsString(jcontent))
            {
                char resolved[512] = {0};

                if (jpath->valuestring[0] == '/')
                {
                    strncpy(resolved, jpath->valuestring, sizeof(resolved) - 1);
                }
                else
                {
                    char proc_dir[256] = {0};

                    if (0 == func_proc_self_dir(proc_dir, sizeof(proc_dir)))
                    {
                        snprintf(resolved, sizeof(resolved), "%s/%s", proc_dir, jpath->valuestring);
                    }
                    else
                    {
                        strncpy(resolved, jpath->valuestring, sizeof(resolved) - 1);
                    }
                }

                FILE *fp = fopen(resolved, "wb");

                if (fp)
                {
                    fwrite(jcontent->valuestring, 1, strlen(jcontent->valuestring), fp);
                    fclose(fp);
                    cJSON_AddBoolToObject(resp, "success", 1);
                    cJSON_AddStringToObject(resp, "path", jpath->valuestring);
                }
            }

            char *p_tx = cJSON_PrintUnformatted(resp);

            if (p_tx)
            {
                ws_send_one(c->id, p_tx, strlen(p_tx));
                free(p_tx);
            }

            cJSON_Delete(resp);
            cJSON_Delete(root);

            return;
        }
    }

    /* ---- 信号 CRUD 命令 ---- */
    cJSON *jsaddr   = cJSON_GetObjectItem(root, "saddr");
    cJSON *jsigtype = cJSON_GetObjectItem(root, "signal_type");
    cJSON *jcurd    = cJSON_GetObjectItem(root, "curd");
    cJSON *jsigdata = cJSON_GetObjectItem(root, "signal_data");
    cJSON *jzone    = cJSON_GetObjectItem(root, "setting_zone");

    if (!jsaddr || !cJSON_IsString(jsaddr) || !jsaddr->valuestring)
    {
        cJSON_Delete(root);

        return;
    }

    if (!jsigtype || !cJSON_IsString(jsigtype) || !jsigtype->valuestring)
    {
        cJSON_Delete(root);

        return;
    }

    if (!jcurd || !cJSON_IsString(jcurd) || !jcurd->valuestring)
    {
        cJSON_Delete(root);

        return;
    }

    /* 查找信号类型 */
    enum_ws_sig_type st = WS_SIG_OUT;

    for (int i = 0; i < WS_SIG_MAX; i++)
    {
        if (0 == strcmp(jsigtype->valuestring, g_ws_sig_names[i]))
        {
            st = (enum_ws_sig_type)i;
            break;
        }
    }

    const char *saddr    = jsaddr->valuestring;
    const char *curd     = jcurd->valuestring;
    const char *sig_data = jsigdata && cJSON_IsString(jsigdata) ? jsigdata->valuestring : "";
    uint8_t zone         = jzone && cJSON_IsNumber(jzone) ? (uint8_t)jzone->valueint : 0;

    pthread_mutex_lock(&g_ws_session_mutex);

    stru_ws_session &sess = g_ws_sessions[c->id];

    if (0 == strcmp(curd, "add"))
    {
        if (g_ws_rx_log) LOG_I("WS add %s %s", g_ws_sig_names[st], saddr);
        ws_sig_add(&sess, st, saddr);

        /* 立即推送当前状态 */
        cJSON *push = cJSON_CreateObject();
        bool dummy  = false;

        ws_make_sig_json(&sess, push, st, &dummy);

        char *p_tx = cJSON_PrintUnformatted(push);

        if (p_tx)
        {
            ws_send_one(c->id, p_tx, strlen(p_tx));
            free(p_tx);
        }

        cJSON_Delete(push);
    }
    else if (0 == strcmp(curd, "del"))
    {
        if (g_ws_rx_log) LOG_I("WS del %s %s", g_ws_sig_names[st], saddr);
        ws_sig_del(&sess, st, saddr);

        cJSON *push = cJSON_CreateObject();
        bool dummy  = false;

        ws_make_sig_json(&sess, push, st, &dummy);

        char *p_tx = cJSON_PrintUnformatted(push);

        if (p_tx)
        {
            ws_send_one(c->id, p_tx, strlen(p_tx));
            free(p_tx);
        }

        cJSON_Delete(push);
    }
    else if (0 == strcmp(curd, "set"))
    {
        if (g_ws_rx_log) LOG_I("WS set %s %s = %s (zone=%u)", g_ws_sig_names[st], saddr, sig_data, zone);
        ws_sig_set(&sess, st, saddr, zone, sig_data);
    }

    pthread_mutex_unlock(&g_ws_session_mutex);

    cJSON_Delete(root);
}

/* ---- 会话销毁 ---- */
void ws_session_destroy(struct mg_connection *c)
{
    if (!c)
    {
        return;
    }

    pthread_mutex_lock(&g_ws_session_mutex);
    g_ws_sessions.erase(c->id);
    pthread_mutex_unlock(&g_ws_session_mutex);
}

/* ================================================================
 * web_log 命令 — 控制 WebSocket 收发包日志
 * ================================================================ */

/** @brief web_log 命令补全 */
LOCAL void cmd_web_log_complete(const char *buf, char ***completions, int *ncomp)
{
    static const char *subs[] = {"on", "off", "status"};
    cmd_sub_complete(buf, completions, ncomp, subs, sizeof(subs) / sizeof(subs[0]));
}

/** @brief web_log 命令处理 */
LOCAL void cmd_web_log(int argc, char *argv[])
{
    const char *usage =
        "web_log on     打开 WS 收发包日志\n"
        "web_log off    关闭 WS 收发包日志\n"
        "web_log status 查看当前状态\n";

    if (argc < 2)
    {
        printf("%s", usage);
        return;
    }

    if (0 == strcmp(argv[1], "on"))
    {
        g_ws_rx_log = 1;
        printf("WS log: ON\n");
    }
    else if (0 == strcmp(argv[1], "off"))
    {
        g_ws_rx_log = 0;
        printf("WS log: OFF\n");
    }
    else if (0 == strcmp(argv[1], "status"))
    {
        printf("WS log: %s\n", g_ws_rx_log ? "ON" : "OFF");
    }
    else
    {
        printf("未知子命令: %s\n%s", argv[1], usage);
    }
}

CMD_REGISTER_C("web_log", cmd_web_log, "WebSocket 收发包日志开关", cmd_web_log_complete);
