#include <glib.h>
#include <string.h>
#include <json-glib/json-glib.h>

#include "purple.h"
#include "plugin.h"
#include "version.h"
#include "account.h"
#include "accountopt.h"
#include "blist.h"
#include "conversation.h"
#include "debug.h"
#include "notify.h"
#include "privacy.h"
#include "prpl.h"
#include "roomlist.h"
#include "status.h"
#include "util.h"
#include "prefs.h"

#define MESHCORE_PLUGIN_ID "prpl-meshcore"

/* Plugin data structure */
typedef struct {
    PurpleConnection *gc;
    gchar *device_path;
    gint baudrate;
    gboolean connected;
    guint message_timer;
    guint discovery_timer;
    GHashTable *joined_chats;
    GHashTable *chat_users;
    GHashTable *pidgin_to_meshcore_id;  // Maps Pidgin chat ID → meshcore channel_idx
} MeshcoreData;

/* Forward declarations */
static void meshcore_login(PurpleAccount *account);
static void meshcore_close(PurpleConnection *gc);
static int meshcore_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags);
static void meshcore_set_status(PurpleAccount *account, PurpleStatus *status);
static GList *meshcore_status_types(PurpleAccount *account);
static GList *meshcore_chat_info(PurpleConnection *gc);
static GHashTable *meshcore_chat_info_defaults(PurpleConnection *gc, const char *chat_name);
static void meshcore_join_chat(PurpleConnection *gc, GHashTable *components);
static int meshcore_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags);
static const char *meshcore_list_icon(PurpleAccount *account, PurpleBuddy *buddy);

/* HTML entity decoding function - ADDED FOR FIX */
static gchar *decode_html_entities(const gchar *input) {
    if (!input) return NULL;

    gchar *result = g_strdup(input);
    gchar *temp;

    /* Replace common HTML entities */
    temp = result;
    result = purple_strreplace(temp, "&lt;", "<");
    g_free(temp);

    temp = result;
    result = purple_strreplace(temp, "&gt;", ">");
    g_free(temp);

    temp = result;
    result = purple_strreplace(temp, "&amp;", "&");
    g_free(temp);

    temp = result;
    result = purple_strreplace(temp, "&quot;", "\"");
    g_free(temp);

    temp = result;
    result = purple_strreplace(temp, "&#39;", "'");
    g_free(temp);

    return result;
}

/* Execute meshcore-cli command */
static gchar *execute_meshcore_cli(MeshcoreData *mdata, const gchar *command) {
    gchar *full_command;
    gchar *output = NULL;
    GError *error = NULL;
    gint exit_status;

    if (!mdata || !command) {
        return NULL;
    }

    /* Use -j for JSON output AND -s for serial device */
    full_command = g_strdup_printf("meshcore-cli -j -s %s -b %d %s", 
                                   mdata->device_path, mdata->baudrate, command);

    purple_debug_info("meshcore", "Executing: %s\n", full_command);

    if (g_spawn_command_line_sync(full_command, &output, NULL, &exit_status, &error)) {
        if (exit_status == 0) {
            purple_debug_info("meshcore", "Command output: %s\n", output ? output : "(null)");
        } else {
            purple_debug_warning("meshcore", "Command failed with exit status %d\n", exit_status);
            g_free(output);
            output = NULL;
        }
    } else {
        purple_debug_error("meshcore", "Failed to execute command: %s\n", error->message);
        g_error_free(error);
    }

    g_free(full_command);
    return output;
}

/* List icon */
static const char *meshcore_list_icon(PurpleAccount *account, PurpleBuddy *buddy) {
    return "meshcore";
}

/* Create default buddy list entries */
static void create_default_buddies(MeshcoreData *mdata) {
    PurpleAccount *account = purple_connection_get_account(mdata->gc);
    PurpleGroup *meshcore_group;

    /* Create Meshcore group */
    meshcore_group = purple_find_group("Meshcore");
    if (!meshcore_group) {
        meshcore_group = purple_group_new("Meshcore");
        purple_blist_add_group(meshcore_group, NULL);
    }

    purple_debug_info("meshcore", "Created Meshcore buddy group\n");
}

/* Debug channels JSON structure */
static void debug_channels_json(MeshcoreData *mdata) {
    gchar *channels_output = execute_meshcore_cli(mdata, "get_channels");
    if (channels_output) {
        purple_debug_info("meshcore", "Raw channels JSON: %s\n", channels_output);
        g_free(channels_output);
    } else {
        purple_debug_warning("meshcore", "Failed to get channels for debugging\n");
    }
}

/* Track users dynamically from received messages */
static void add_user_to_channel(MeshcoreData *mdata, int channel_id, const gchar *username) {
    if (!username || strlen(username) == 0) return;

    PurpleConversation *conv = purple_find_chat(mdata->gc, channel_id);
    if (!conv) return;

    PurpleConvChat *chat = purple_conversation_get_chat_data(conv);
    if (!chat) return;

    /* Check if user is already in the chat */
    if (purple_conv_chat_find_user(chat, username)) {
        return; /* User already exists */
    }

    /* Add the user to the chat */
    purple_conv_chat_add_user(chat, username, NULL, PURPLE_CBFLAGS_NONE, FALSE);
    purple_debug_info("meshcore", "Added new user '%s' to channel %d\n", username, channel_id);
}

/* No default users - purely dynamic user tracking */
static void populate_channel_users(MeshcoreData *mdata, int channel_id) {
    purple_debug_info("meshcore", "Channel %d ready for dynamic user tracking (no default users)\n", channel_id);
    /* Users will be added dynamically when they send messages */
}

/* Parse JSON channels and add to buddy list */
static void parse_and_add_channels(MeshcoreData *mdata, const gchar *json_output) {
    JsonParser *parser;
    JsonNode *root;
    JsonArray *channels_array = NULL;
    GError *error = NULL;

    if (!json_output || strlen(json_output) == 0) {
        purple_debug_warning("meshcore", "Empty channels JSON output\n");
        return;
    }

    parser = json_parser_new();

    if (!json_parser_load_from_data(parser, json_output, -1, &error)) {
        purple_debug_error("meshcore", "Failed to parse channels JSON: %s\n", error->message);
        g_error_free(error);
        g_object_unref(parser);
        return;
    }

    root = json_parser_get_root(parser);

    /* Handle both array and object formats */
    if (JSON_NODE_HOLDS_ARRAY(root)) {
        channels_array = json_node_get_array(root);
    } else if (JSON_NODE_HOLDS_OBJECT(root)) {
        JsonObject *root_obj = json_node_get_object(root);
        if (json_object_has_member(root_obj, "channels")) {
            JsonNode *channels_node = json_object_get_member(root_obj, "channels");
            if (JSON_NODE_HOLDS_ARRAY(channels_node)) {
                channels_array = json_node_get_array(channels_node);
            }
        }
    }

    if (!channels_array) {
        purple_debug_error("meshcore", "Could not find channels array in JSON\n");
        g_object_unref(parser);
        return;
    }

    /* Create Meshcore Channels group */
    PurpleAccount *account = purple_connection_get_account(mdata->gc);
    PurpleGroup *channels_group = purple_find_group("Meshcore Channels");
    if (!channels_group) {
        channels_group = purple_group_new("Meshcore Channels");
        purple_blist_add_group(channels_group, NULL);
    }

    for (guint i = 0; i < json_array_get_length(channels_array); i++) {
        JsonNode *channel_node = json_array_get_element(channels_array, i);
        if (!JSON_NODE_HOLDS_OBJECT(channel_node)) continue;

        JsonObject *channel_obj = json_node_get_object(channel_node);

        /* Get channel index from JSON or use array index */
        gint64 index = i;
        if (json_object_has_member(channel_obj, "channel_idx")) {
            index = json_object_get_int_member(channel_obj, "channel_idx");
        }

        /* Get channel name */
        const gchar *channel_name = NULL;
        if (json_object_has_member(channel_obj, "channel_name")) {
            channel_name = json_object_get_string_member(channel_obj, "channel_name");
        }

        /* DEBUG: Show raw channel data */
        purple_debug_info("meshcore", "RAW CHANNEL DATA: array_index=%d, channel_idx=%ld, channel_name='%s'\n", 
                          i, index, channel_name ? channel_name : "(null)");

        /* Skip channels with empty names (except for index 0) */
        if (index != 0 && (!channel_name || strlen(channel_name) == 0)) {
            purple_debug_info("meshcore", "Skipping channel %ld with empty name\n", index);
            continue;
        }

        /* Determine display name */
        gchar *display_name;
        if (channel_name && strlen(channel_name) > 0) {
            display_name = g_strdup(channel_name);
        } else {
            display_name = g_strdup("Public");
        }

        purple_debug_info("meshcore", "Processing channel %ld: '%s'\n", index, display_name);

        /* Create components with BOTH channel name AND channel_id */
        GHashTable *components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
        g_hash_table_insert(components, "channel", g_strdup(display_name));
        g_hash_table_insert(components, "channel_id", g_strdup_printf("%ld", index));

        /* Check if chat already exists in buddy list */
        PurpleChat *existing_chat = purple_blist_find_chat(account, display_name);
        if (!existing_chat) {
            /* Create new chat for buddy list */
            PurpleChat *chat = purple_chat_new(account, display_name, components);
            purple_blist_add_chat(chat, channels_group, NULL);
            purple_debug_info("meshcore", "Added NEW chat '%s' (ID: %ld) to buddy list\n", display_name, index);
        } else {
            purple_debug_info("meshcore", "Chat '%s' already exists in buddy list\n", display_name);
            g_hash_table_destroy(components); /* Clean up if not used */
        }

        /* Only auto-join if not already joined */
        PurpleConversation *existing_conv = purple_find_chat(mdata->gc, (int)index);
        if (!existing_conv) {
            purple_debug_info("meshcore", "Auto-joining channel %ld: '%s'\n", index, display_name);
            GHashTable *join_components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
            g_hash_table_insert(join_components, "channel", g_strdup(display_name));
            g_hash_table_insert(join_components, "channel_id", g_strdup_printf("%ld", index));
            meshcore_join_chat(mdata->gc, join_components);

            /* VERIFY: Check if conversation was created with correct ID */
            PurpleConversation *verify_conv = purple_find_chat(mdata->gc, (int)index);
            purple_debug_info("meshcore", "Verification: Channel %ld conversation created: %s\n", 
                              index, verify_conv ? "YES" : "NO");

            g_hash_table_destroy(join_components);
        } else {
            purple_debug_info("meshcore", "Channel %ld already joined, skipping\n", index);
        }

        g_free(display_name);
    }

    g_object_unref(parser);
}

/* Network discovery function */
static gboolean meshcore_discover_network(gpointer data) {
    MeshcoreData *mdata = (MeshcoreData *)data;

    if (!mdata || !mdata->connected) {
        return FALSE;
    }

    purple_debug_info("meshcore", "=== STARTING NETWORK DISCOVERY ===\n");

    /* Get channels */
    gchar *channels_output = execute_meshcore_cli(mdata, "get_channels");
    if (channels_output) {
        purple_debug_info("meshcore", "=== RAW CHANNELS JSON ===\n%s\n=== END JSON ===\n", channels_output);
        parse_and_add_channels(mdata, channels_output);
        g_free(channels_output);
    } else {
        purple_debug_warning("meshcore", "Failed to discover channels\n");
    }

    purple_debug_info("meshcore", "=== NETWORK DISCOVERY COMPLETE ===\n");
    return TRUE;
}

/* Check for incoming messages */
static gboolean meshcore_check_messages(gpointer data) {
    MeshcoreData *mdata = (MeshcoreData *)data;
    gchar *output;

    if (!mdata || !mdata->connected) {
        return FALSE;
    }

    output = execute_meshcore_cli(mdata, "recv");
    if (output && strlen(output) > 0) {
        purple_debug_info("meshcore", "Raw message JSON: %s\n", output);

        JsonParser *parser = json_parser_new();
        GError *error = NULL;

        if (json_parser_load_from_data(parser, output, -1, &error)) {
            JsonNode *root = json_parser_get_root(parser);

            if (JSON_NODE_HOLDS_OBJECT(root)) {
                JsonObject *msg_obj = json_node_get_object(root);

                /* Extract message data */
                const gchar *text = NULL;
                gchar *sender = NULL;
                gchar **text_parts = NULL;
                gint64 channel_idx = 0;
                gint64 path_len = 0;
                gdouble snr = 0.0;

                if (json_object_has_member(msg_obj, "text")) {
                    text = json_object_get_string_member(msg_obj, "text");
                }

                /* FIXED: Extract sender from text field */
                if (text) {
                    text_parts = g_strsplit(text, ": ", 2);
                    if (text_parts && text_parts[0] && text_parts[1]) {
                        sender = g_strdup(text_parts[0]);  // First part is sender
                        text = text_parts[1];              // Second part is actual message
                        purple_debug_info("meshcore", "Extracted sender: '%s', message: '%s'\n", sender, text);
                    } else {
                        sender = g_strdup("Unknown");
                        purple_debug_info("meshcore", "Could not extract sender from: '%s'\n", text);
                    }
                }

                if (json_object_has_member(msg_obj, "channel_idx")) {
                    channel_idx = json_object_get_int_member(msg_obj, "channel_idx");
                }

                if (json_object_has_member(msg_obj, "path_len")) {
                    path_len = json_object_get_int_member(msg_obj, "path_len");
                }

                if (json_object_has_member(msg_obj, "SNR")) {
                    snr = json_object_get_double_member(msg_obj, "SNR");
                }

                if (text && sender) {
                    /* Add the sender to the channel user list if not already there */
                    add_user_to_channel(mdata, (int)channel_idx, sender);

                    /* Format message with routing info */
                    gchar *formatted_message;
                    if (path_len > 0 || snr != 0.0) {
                        formatted_message = g_strdup_printf("%s [%ld hops, SNR: %.1fdB]", 
                                                           text, path_len, snr);
                    } else {
                        formatted_message = g_strdup(text);
                    }

                    /* Send to appropriate chat */
                    PurpleConversation *conv = purple_find_chat(mdata->gc, (int)channel_idx);
                    purple_debug_info("meshcore", "Looking for conversation with ID %ld, found: %s\n", 
                                      channel_idx, conv ? "YES" : "NO");
                    if (conv) {
                        serv_got_chat_in(mdata->gc, (int)channel_idx, sender, PURPLE_MESSAGE_RECV, formatted_message, time(NULL));
                        purple_debug_info("meshcore", "Delivered message from %s to channel %ld: %s\n", 
                                         sender, channel_idx, formatted_message);
                    } else {
                        purple_debug_warning("meshcore", "No conversation found for channel %ld\n", channel_idx);
                    }

                    g_free(formatted_message);
                }

                /* Clean up */
                if (sender) g_free(sender);
                if (text_parts) g_strfreev(text_parts);
            }
        } else {
            purple_debug_error("meshcore", "Failed to parse message JSON: %s\n", error->message);
            g_error_free(error);
        }

        g_object_unref(parser);
        g_free(output);
    }

    return TRUE;
}

/* Login function */
static void meshcore_login(PurpleAccount *account) {
    PurpleConnection *gc = purple_account_get_connection(account);
    MeshcoreData *mdata;
    const gchar *device_path;
    gint baudrate;

    purple_debug_info("meshcore", "=== MESHCORE LOGIN STARTED ===\n");

    /* Allocate plugin data */
    mdata = g_new0(MeshcoreData, 1);
    mdata->gc = gc;
    mdata->connected = FALSE;
    mdata->message_timer = 0;
    mdata->joined_chats = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
    mdata->chat_users = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_hash_table_destroy);
    mdata->pidgin_to_meshcore_id = g_hash_table_new(g_direct_hash, g_direct_equal);

    /* Get settings */
    device_path = purple_account_get_string(account, "device_path", "/dev/ttyUSB0");
    baudrate = purple_account_get_int(account, "baudrate", 115200);

    mdata->device_path = g_strdup(device_path);
    mdata->baudrate = baudrate;

    purple_connection_set_protocol_data(gc, mdata);
    purple_connection_set_state(gc, PURPLE_CONNECTING);

    purple_debug_info("meshcore", "Device: %s at %d baud\n", mdata->device_path, mdata->baudrate);

    /* Test meshcore-cli connectivity */
    gchar *test_output = execute_meshcore_cli(mdata, "infos");
    if (!test_output) {
        purple_debug_warning("meshcore", "meshcore-cli not responding, but continuing anyway\n");
    } else {
        purple_debug_info("meshcore", "meshcore-cli is working\n");
        g_free(test_output);
    }

    /* Set connected state */
    mdata->connected = TRUE;
    purple_connection_set_state(gc, PURPLE_CONNECTED);

    /* Set account status to available/online */
    purple_account_set_status(account, "available", TRUE, NULL);
    purple_debug_info("meshcore", "Account status set to available\n");

    /* Create default buddy list */
    create_default_buddies(mdata);

    /* Debug channels JSON structure */
    debug_channels_json(mdata);

    /* Start immediate discovery - this will auto-join channels */
    meshcore_discover_network(mdata);

    /* Start message monitoring - polling every 2 seconds */
    mdata->message_timer = g_timeout_add_seconds(2, meshcore_check_messages, mdata);
    purple_debug_info("meshcore", "Started message monitoring (polling every 2 seconds)\n");

    /* Start periodic discovery timer (every 60 seconds) */
    mdata->discovery_timer = g_timeout_add_seconds(60, meshcore_discover_network, mdata);

    purple_debug_info("meshcore", "=== MESHCORE LOGIN COMPLETE ===\n");
}

/* Close connection */
static void meshcore_close(PurpleConnection *gc) {
    MeshcoreData *mdata = purple_connection_get_protocol_data(gc);

    purple_debug_info("meshcore", "=== MESHCORE LOGOUT STARTED ===\n");

    if (mdata) {
        /* Stop timers */
        if (mdata->message_timer) {
            g_source_remove(mdata->message_timer);
            mdata->message_timer = 0;
        }

        if (mdata->discovery_timer) {
            g_source_remove(mdata->discovery_timer);
            mdata->discovery_timer = 0;
        }

        /* Clean up data */
        if (mdata->joined_chats) {
            g_hash_table_destroy(mdata->joined_chats);
        }

        if (mdata->chat_users) {
            g_hash_table_destroy(mdata->chat_users);
        }

        if (mdata->pidgin_to_meshcore_id) {
            g_hash_table_destroy(mdata->pidgin_to_meshcore_id);
        }

        g_free(mdata->device_path);
        g_free(mdata);
    }

    purple_debug_info("meshcore", "=== MESHCORE LOGOUT COMPLETE ===\n");
}

/* Send IM message */
static int meshcore_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags) {
    MeshcoreData *mdata = purple_connection_get_protocol_data(gc);
    gchar *command;
    gchar *output;

    purple_debug_info("meshcore", "Sending IM to %s: %s\n", who, message);

    command = g_strdup_printf("send_direct \"%s\" \"%s\"", who, message);
    output = execute_meshcore_cli(mdata, command);

    if (output) {
        purple_debug_info("meshcore", "Direct message sent successfully\n");
        g_free(output);
        g_free(command);
        return 1;
    } else {
        purple_debug_error("meshcore", "Failed to send direct message\n");
        g_free(command);
        return -1;
    }
}

/* Set status */
static void meshcore_set_status(PurpleAccount *account, PurpleStatus *status) {
    purple_debug_info("meshcore", "Status changed to: %s\n", purple_status_get_name(status));
}

/* Status types */
static GList *meshcore_status_types(PurpleAccount *account) {
    GList *types = NULL;
    PurpleStatusType *status;

    status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE);
    types = g_list_append(types, status);

    status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
    types = g_list_append(types, status);

    return types;
}

/* Chat info */
static GList *meshcore_chat_info(PurpleConnection *gc) {
    GList *m = NULL;
    struct proto_chat_entry *pce;

    pce = g_new0(struct proto_chat_entry, 1);
    pce->label = "Channel";
    pce->identifier = "channel";
    pce->required = TRUE;
    m = g_list_append(m, pce);

    /* Add channel_id field for internal use */
    pce = g_new0(struct proto_chat_entry, 1);
    pce->label = "Channel ID";
    pce->identifier = "channel_id";
    pce->required = FALSE;
    m = g_list_append(m, pce);

    return m;
}

/* Chat info defaults */
static GHashTable *meshcore_chat_info_defaults(PurpleConnection *gc, const char *chat_name) {
    GHashTable *defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);

    if (chat_name) {
        g_hash_table_insert(defaults, "channel", g_strdup(chat_name));
    } else {
        g_hash_table_insert(defaults, "channel", g_strdup("Public"));
    }

    return defaults;
}

/* Join chat with correct channel ID mapping */
static void meshcore_join_chat(PurpleConnection *gc, GHashTable *components) {
    MeshcoreData *mdata = purple_connection_get_protocol_data(gc);
    const char *channel_name = g_hash_table_lookup(components, "channel");
    const char *channel_id_str = g_hash_table_lookup(components, "channel_id");

    if (channel_name) {
        int meshcore_channel_id = 0;

        /* Use the actual channel_id if provided */
        if (channel_id_str) {
            meshcore_channel_id = atoi(channel_id_str);
            purple_debug_info("meshcore", "Using provided channel ID: %d for '%s'\n", meshcore_channel_id, channel_name);
        } else {
            /* Fallback: Extract channel number from name (old logic) */
            if (g_str_has_prefix(channel_name, "Channel_")) {
                meshcore_channel_id = atoi(channel_name + 8);
            } else if (g_str_has_prefix(channel_name, "#")) {
                /* Map named channels to IDs */
                if (g_str_equal(channel_name, "#bot")) meshcore_channel_id = 2;
                else if (g_str_equal(channel_name, "#testing")) meshcore_channel_id = 3;
                else meshcore_channel_id = 1;
            } else if (g_str_equal(channel_name, "Public")) {
                meshcore_channel_id = 0;
            }
            purple_debug_info("meshcore", "Extracted channel ID: %d from name '%s'\n", meshcore_channel_id, channel_name);
        }

        /* Track joined chats */
        g_hash_table_insert(mdata->joined_chats, GINT_TO_POINTER(meshcore_channel_id), g_strdup(channel_name));

        /* Notify libpurple that we've joined the chat (returns void) */
        serv_got_joined_chat(gc, meshcore_channel_id, channel_name);

        /* The Pidgin chat ID is the same as meshcore_channel_id since we passed it as the ID */
        int pidgin_chat_id = meshcore_channel_id;

        /* CRITICAL: Store the mapping from Pidgin chat ID to meshcore channel_idx */
        g_hash_table_insert(mdata->pidgin_to_meshcore_id, 
                           GINT_TO_POINTER(pidgin_chat_id), 
                           GINT_TO_POINTER(meshcore_channel_id));

        purple_debug_info("meshcore", "MAPPING: Pidgin chat ID %d → meshcore channel %d (%s)\n", 
                         pidgin_chat_id, meshcore_channel_id, channel_name);

        /* Add yourself to the chat first */
        PurpleConversation *conv = purple_find_chat(gc, pidgin_chat_id);
        if (conv) {
            PurpleConvChat *chat = purple_conversation_get_chat_data(conv);
            if (chat) {
                PurpleAccount *account = purple_connection_get_account(gc);
                const gchar *my_username = purple_account_get_username(account);

                /* Add yourself to the chat */
                purple_conv_chat_add_user(chat, my_username, NULL, PURPLE_CBFLAGS_NONE, FALSE);
                purple_debug_info("meshcore", "Added self (%s) to chat %d\n", my_username, pidgin_chat_id);
            }
        }

        purple_debug_info("meshcore", "Joined chat room: %s (Pidgin ID: %d, Meshcore ID: %d)\n", 
                         channel_name, pidgin_chat_id, meshcore_channel_id);

        /* Setup for dynamic user tracking (no default users) */
        populate_channel_users(mdata, meshcore_channel_id);
    }
}

/* Send chat message - FIXED TO DECODE HTML ENTITIES */
static int meshcore_chat_send(PurpleConnection *gc, int pidgin_chat_id, const char *message, PurpleMessageFlags flags) {
    MeshcoreData *mdata = purple_connection_get_protocol_data(gc);

    /* DECODE HTML ENTITIES BEFORE SENDING TO MESHCORE-CLI */
    gchar *decoded_message = decode_html_entities(message);

    /* Look up the actual meshcore channel ID */
    gpointer meshcore_channel_ptr = g_hash_table_lookup(mdata->pidgin_to_meshcore_id, GINT_TO_POINTER(pidgin_chat_id));
    int meshcore_channel_id = GPOINTER_TO_INT(meshcore_channel_ptr);

    purple_debug_info("meshcore", "Sending message: Pidgin chat ID %d → meshcore channel %d: %s (decoded: %s)\n", 
                     pidgin_chat_id, meshcore_channel_id, message, decoded_message);

    gchar *command;
    if (meshcore_channel_id == 0) {
        /* Use 'public' command for channel 0 with DECODED message */
        command = g_strdup_printf("public \"%s\"", decoded_message);
    } else {
        /* Use 'chan' command for other channels with DECODED message */
        command = g_strdup_printf("chan %d \"%s\"", meshcore_channel_id, decoded_message);
    }

    gchar *output = execute_meshcore_cli(mdata, command);

    if (output) {
        purple_debug_info("meshcore", "Channel message sent successfully to meshcore channel %d\n", meshcore_channel_id);

        /* Echo back using the Pidgin chat ID with ORIGINAL message (so user sees what they typed) */
        PurpleAccount *account = purple_connection_get_account(gc);
        const gchar *my_username = purple_account_get_username(account);
        serv_got_chat_in(gc, pidgin_chat_id, my_username, PURPLE_MESSAGE_SEND, message, time(NULL));

        g_free(output);
        g_free(command);
        g_free(decoded_message);
        return 0;
    } else {
        purple_debug_error("meshcore", "Failed to send channel message to meshcore channel %d\n", meshcore_channel_id);
        g_free(command);
        g_free(decoded_message);
        return -1;
    }
}

/* Protocol info structure with correct field order */
static PurplePluginProtocolInfo prpl_info = {
    OPT_PROTO_CHAT_TOPIC,           /* options */
    NULL,                           /* user_splits */
    NULL,                           /* protocol_options */
    NO_BUDDY_ICONS,                 /* icon_spec */
    meshcore_list_icon,             /* list_icon */
    NULL,                           /* list_emblem */
    NULL,                           /* status_text */
    NULL,                           /* tooltip_text */
    meshcore_status_types,          /* status_types */
    NULL,                           /* blist_node_menu */
    meshcore_chat_info,             /* chat_info */
    meshcore_chat_info_defaults,    /* chat_info_defaults */
    meshcore_login,                 /* login */
    meshcore_close,                 /* close */
    meshcore_send_im,               /* send_im */
    NULL,                           /* set_info */
    NULL,                           /* send_typing */
    NULL,                           /* get_info */
    meshcore_set_status,            /* set_status */
    NULL,                           /* set_idle */
    NULL,                           /* change_passwd */
    NULL,                           /* add_buddy */
    NULL,                           /* add_buddies */
    NULL,                           /* remove_buddy */
    NULL,                           /* remove_buddies */
    NULL,                           /* add_permit */
    NULL,                           /* add_deny */
    NULL,                           /* rem_permit */
    NULL,                           /* rem_deny */
    NULL,                           /* set_permit_deny */
    meshcore_join_chat,             /* join_chat */
    NULL,                           /* reject_chat */
    NULL,                           /* get_chat_name */
    NULL,                           /* chat_invite */
    NULL,                           /* chat_leave */
    NULL,                           /* chat_whisper */
    meshcore_chat_send,             /* chat_send */
    NULL,                           /* keepalive */
    NULL,                           /* register_user */
    NULL,                           /* get_cb_info */
    NULL,                           /* get_cb_away */
    NULL,                           /* alias_buddy */
    NULL,                           /* group_buddy */
    NULL,                           /* rename_group */
    NULL,                           /* buddy_free */
    NULL,                           /* convo_closed */
    NULL,                           /* normalize */
    NULL,                           /* set_buddy_icon */
    NULL,                           /* remove_group */
    NULL,                           /* get_cb_real_name */
    NULL,                           /* set_chat_topic */
    NULL,                           /* find_blist_chat */
    NULL,                           /* roomlist_get_list */
    NULL,                           /* roomlist_cancel */
    NULL,                           /* roomlist_expand_category */
    NULL,                           /* can_receive_file */
    NULL,                           /* send_file */
    NULL,                           /* new_xfer */
    NULL,                           /* offline_message */
    NULL,                           /* whiteboard_prpl_ops */
    NULL,                           /* send_raw */
    NULL,                           /* roomlist_room_serialize */
    NULL,                           /* unregister_user */
    NULL,                           /* send_attention */
    NULL,                           /* get_attention_types */
    sizeof(PurplePluginProtocolInfo), /* struct_size */
    NULL,                           /* get_account_text_table */
    NULL,                           /* initiate_media */
    NULL,                           /* get_media_caps */
    NULL,                           /* get_moods */
    NULL,                           /* set_public_alias */
    NULL,                           /* get_public_alias */
    NULL,                           /* add_buddy_with_invite */
    NULL                            /* add_buddies_with_invite */
};

/* Plugin info */
static PurplePluginInfo info = {
    PURPLE_PLUGIN_MAGIC,
    PURPLE_MAJOR_VERSION,
    PURPLE_MINOR_VERSION,
    PURPLE_PLUGIN_PROTOCOL,
    NULL,
    0,
    NULL,
    PURPLE_PRIORITY_DEFAULT,
    MESHCORE_PLUGIN_ID,
    "Meshcore",
    "1.0",
    "Meshcore mesh network protocol",
    "Connect to Meshcore mesh networks via meshcore-cli",
    "Your Name <your.email@example.com>",
    "https://github.com/yourusername/purple-meshcore",
    NULL,                   /* load */
    NULL,                   /* unload */
    NULL,                   /* destroy */
    NULL,                   /* ui_info */
    &prpl_info,             /* extra_info */
    NULL,                   /* prefs_info */
    NULL,                   /* actions */
    NULL,                   /* padding... */
    NULL,
    NULL,
    NULL
};

/* Plugin initialization */
static void init_plugin(PurplePlugin *plugin) {
    PurpleAccountOption *option;

    /* Device path option */
    option = purple_account_option_string_new("Device Path", "device_path", "/dev/ttyUSB0");
    prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);

    /* Baudrate option */
    option = purple_account_option_int_new("Baudrate", "baudrate", 115200);
    prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
}

PURPLE_INIT_PLUGIN(meshcore, init_plugin, info)
