#define _DEFAULT_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "irc_client.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <time.h>

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
typedef int socklen_t;
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif

#ifndef ANET_IRC_NO_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#else
typedef void SSL;
typedef void SSL_CTX;
#endif

static SSL_CTX *ssl_ctx_global = NULL;

static void irc_process_line(irc_client_t *client, const char *line);
static void irc_parse_names_response(irc_client_t *client, const char *names_str);
static void irc_parse_privmsg(irc_client_t *client, const char *line);
static void irc_parse_ctcp(irc_client_t *client, const char *line);
static void irc_handle_433_nick_in_use(irc_client_t *client, const char *line);
static int irc_send_raw(irc_client_t *client, const char *command, int force_when_disconnecting);

static void msleep_int(int ms) {
#ifdef _WIN32
    Sleep(ms);
#else
    struct timespec ts;
    ts.tv_sec = ms / 1000;
    ts.tv_nsec = (long)(ms % 1000) * 1000000L;
    nanosleep(&ts, NULL);
#endif
}

static void socket_close_portable(int fd) {
    if (fd < 0) return;
#ifdef _WIN32
    closesocket(fd);
#else
    close(fd);
#endif
}

static void socket_shutdown_portable(int fd) {
    if (fd < 0) return;
#ifdef _WIN32
    shutdown(fd, SD_BOTH);
#else
    shutdown(fd, SHUT_RDWR);
#endif
}

static char *dup_cstr(const char *s) {
    if (!s) return NULL;
    size_t n = strlen(s) + 1;
    char *copy = (char *)malloc(n);
    if (copy) memcpy(copy, s, n);
    return copy;
}

static void initialize_ssl(void) {
#ifndef ANET_IRC_NO_OPENSSL
    if (!ssl_ctx_global) {
        SSL_library_init();
        SSL_load_error_strings();
        OpenSSL_add_all_algorithms();
        ssl_ctx_global = SSL_CTX_new(TLS_client_method());
        if (ssl_ctx_global) {
            SSL_CTX_set_min_proto_version(ssl_ctx_global, TLS1_2_VERSION);
            SSL_CTX_set_default_verify_paths(ssl_ctx_global);
            SSL_CTX_set_verify(ssl_ctx_global, SSL_VERIFY_NONE, NULL);
        }
    }
#endif
}

int irc_logger_init(irc_logger_t *logger, const char *filename) {
    if (!logger || !filename) return -1;
    memset(logger, 0, sizeof(*logger));
    strncpy(logger->filename, filename, sizeof(logger->filename) - 1);
    logger->filename[sizeof(logger->filename) - 1] = '\0';
    logger->file = fopen(filename, "a");
    logger->enabled = (logger->file != NULL);
    if (logger->enabled) {
        time_t now = time(NULL);
        struct tm *tm_info = localtime(&now);
        char timestamp[32];
        strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
        fprintf(logger->file, "\n=== Session started %s ===\n", timestamp);
        fflush(logger->file);
    }
    return logger->enabled ? 0 : -1;
}

void irc_logger_write(irc_logger_t *logger, const char *format, ...) {
    if (!logger || !logger->enabled || !logger->file || !format) return;
    time_t now = time(NULL);
    struct tm *tm_info = localtime(&now);
    char timestamp[16];
    strftime(timestamp, sizeof(timestamp), "[%H:%M:%S]", tm_info);
    fprintf(logger->file, "%s ", timestamp);
    va_list args;
    va_start(args, format);
    vfprintf(logger->file, format, args);
    va_end(args);
    fflush(logger->file);
}

void irc_logger_cleanup(irc_logger_t *logger) {
    if (!logger) return;
    if (logger->file) {
        fprintf(logger->file, "=== Session ended ===\n\n");
        fclose(logger->file);
        logger->file = NULL;
    }
    logger->enabled = 0;
}

static int connect_socket(const char *server, int port) {
#ifdef _WIN32
    static int wsa_ready = 0;
    if (!wsa_ready) {
        WSADATA wsa_data;
        if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) return -1;
        wsa_ready = 1;
    }
#endif

    struct addrinfo hints;
    struct addrinfo *res = NULL;
    struct addrinfo *p = NULL;
    int sock = -1;
    char port_str[16];

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    snprintf(port_str, sizeof(port_str), "%d", port);
    if (getaddrinfo(server, port_str, &hints, &res) != 0) return -1;

    for (p = res; p != NULL; p = p->ai_next) {
        sock = (int)socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (sock < 0) continue;

        int opt = 1;
        setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (const char *)&opt, sizeof(opt));
#ifdef TCP_NODELAY
        setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt));
#endif
#ifdef _WIN32
        DWORD snd_timeout_ms = 15000;
        setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *)&snd_timeout_ms, sizeof(snd_timeout_ms));
#else
        struct timeval tv;
        tv.tv_sec = 15;
        tv.tv_usec = 0;
        setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
#endif

        if (connect(sock, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) break;
        socket_close_portable(sock);
        sock = -1;
    }

    freeaddrinfo(res);
    return sock;
}

static int irc_sock_send_all(irc_client_t *client, const char *buf, size_t len) {
    if (!client || client->socket < 0) return -1;

#ifndef ANET_IRC_NO_OPENSSL
    if (client->use_ssl && client->ssl_conn) {
        size_t sent = 0;
        while (sent < len) {
            int w = SSL_write((SSL *)client->ssl_conn, buf + sent, (int)(len - sent));
            if (w <= 0) {
                int err = SSL_get_error((SSL *)client->ssl_conn, w);
                if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
                    msleep_int(20);
                    continue;
                }
                return -1;
            }
            sent += (size_t)w;
        }
        return 0;
    }
#endif

    size_t sent = 0;
    while (sent < len) {
#ifdef _WIN32
        int w = send(client->socket, buf + sent, (int)(len - sent), 0);
#else
        ssize_t w = send(client->socket, buf + sent, len - sent, 0);
#endif
        if (w <= 0) {
#ifdef _WIN32
            int e = WSAGetLastError();
            if (e == WSAEWOULDBLOCK || e == WSAEINTR) {
                msleep_int(20);
                continue;
            }
#else
            if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
                msleep_int(20);
                continue;
            }
#endif
            return -1;
        }
        sent += (size_t)w;
    }
    return 0;
}

static void set_disconnect_state(irc_client_t *client, const char *reason) {
    if (!client) return;

    pthread_mutex_lock(&client->state_lock);
    client->connected = 0;
    client->thread_active = 0;
    if (client->reconnect_enabled && client->reconnect_attempts < client->reconnect_max_attempts) {
        client->reconnect_next_try = time(NULL) + client->reconnect_delay_seconds;
    }
    int already_reported = client->disconnect_reported;
    client->disconnect_reported = 1;
    pthread_mutex_unlock(&client->state_lock);

    if (!already_reported && client->on_status && reason) client->on_status(client, reason);
    if (reason) irc_logger_write(&client->logger, "%s\n", reason);
}

irc_client_t *irc_client_create(void) {
    irc_client_t *client = (irc_client_t *)calloc(1, sizeof(irc_client_t));
    if (!client) return NULL;

    client->socket = -1;
    client->recv_buffer = (char *)malloc(IRC_BUFFER_SIZE);
    if (!client->recv_buffer) {
        free(client);
        return NULL;
    }
    client->recv_buffer_size = IRC_BUFFER_SIZE;
    client->history_max = 100;
    client->command_history = (char **)calloc((size_t)client->history_max, sizeof(char *));
    pthread_mutex_init(&client->state_lock, NULL);
    initialize_ssl();

    client->reconnect_enabled = 1;
    client->reconnect_max_attempts = 10;
    client->reconnect_delay_seconds = 5;
    return client;
}

void irc_client_destroy(irc_client_t *client) {
    if (!client) return;
    irc_client_disconnect(client);
    irc_logger_cleanup(&client->logger);
    free(client->recv_buffer);
    if (client->command_history) {
        for (int i = 0; i < client->history_count; i++) free(client->command_history[i]);
        free(client->command_history);
    }
    pthread_mutex_destroy(&client->state_lock);
    free(client);
}

static int irc_send_raw(irc_client_t *client, const char *command, int force_when_disconnecting) {
    if (!client || !command || client->socket < 0) return -1;
    if (!force_when_disconnecting && !client->connected) return -1;

    char out[IRC_MAX_MESSAGE + 4];
    size_t n = strlen(command);
    while (n > 0 && (command[n - 1] == '\n' || command[n - 1] == '\r')) n--;
    if (n > IRC_MAX_MESSAGE - 2) n = IRC_MAX_MESSAGE - 2;

    memcpy(out, command, n);
    out[n++] = '\r';
    out[n++] = '\n';
    out[n] = '\0';

    if (irc_sock_send_all(client, out, n) != 0) {
        irc_logger_write(&client->logger, "[SEND FAIL] errno=%d\n", errno);
        if (!force_when_disconnecting) set_disconnect_state(client, "Connection lost while sending");
        return -1;
    }

    irc_logger_write(&client->logger, "[SEND] %s", out);
    return 0;
}

int irc_send_command(irc_client_t *client, const char *command) {
    return irc_send_raw(client, command, 0);
}

int irc_send_message(irc_client_t *client, const char *channel, const char *message) {
    if (!client || !channel || !message || !client->connected) return -1;
    char cmd[IRC_MAX_MESSAGE];
    snprintf(cmd, sizeof(cmd), "PRIVMSG %s :%s", channel, message);
    if (irc_send_command(client, cmd) == 0) {
        irc_logger_write(&client->logger, "<%s> %s\n", client->nickname, message);
        return 0;
    }
    return -1;
}

int irc_join_channel(irc_client_t *client, const char *channel) {
    if (!client || !channel) return -1;
    char cmd[IRC_MAX_MESSAGE];
    snprintf(cmd, sizeof(cmd), "JOIN %s", channel);
    return irc_send_command(client, cmd);
}

int irc_leave_channel(irc_client_t *client, const char *channel) {
    if (!client || !channel) return -1;
    char cmd[IRC_MAX_MESSAGE];
    snprintf(cmd, sizeof(cmd), "PART %s", channel);
    return irc_send_command(client, cmd);
}

int irc_request_names(irc_client_t *client, const char *channel) {
    if (!client || !channel) return -1;
    char cmd[IRC_MAX_MESSAGE];
    snprintf(cmd, sizeof(cmd), "NAMES %s", channel);
    return irc_send_command(client, cmd);
}

int irc_client_try_autojoin(irc_client_t *client) {
    if (!client || !client->connected || !client->registered || !client->motd_complete || !client->autojoin_pending || client->auth_required) {
        return -1;
    }

    client->autojoin_pending = 0;
    irc_logger_write(&client->logger, "Auto-joining %s\n", client->current_channel);
    return irc_join_channel(client, client->current_channel);
}

int irc_client_connect(irc_client_t *client, const char *server, int port,
                       const char *nickname, const char *channel, int use_ssl) {
    if (!client || !server || !nickname || !channel) return -1;

    irc_client_disconnect(client);

    client->registered = 0;
    client->cap_sent = 0;
    client->disconnect_reported = 0;
    client->motd_complete = 0;
    client->autojoin_pending = 1;
    client->auth_required = 0;
    client->welcome_seen = 0;

    strncpy(client->last_server, server, sizeof(client->last_server) - 1);
    client->last_port = port;
    strncpy(client->last_nickname, nickname, sizeof(client->last_nickname) - 1);
    strncpy(client->last_channel, channel, sizeof(client->last_channel) - 1);
    client->last_use_ssl = use_ssl;

    client->socket = connect_socket(server, port);
    if (client->socket < 0) {
        if (client->on_status) client->on_status(client, "Failed to connect to server");
        return -1;
    }

    client->use_ssl = 0;
    client->ssl_conn = NULL;
    if (use_ssl) {
#ifdef ANET_IRC_NO_OPENSSL
        socket_close_portable(client->socket);
        client->socket = -1;
        if (client->on_status) client->on_status(client, "This build was created without TLS/OpenSSL support");
        return -1;
#else
        SSL *ssl = SSL_new(ssl_ctx_global);
        if (!ssl) {
            socket_close_portable(client->socket);
            client->socket = -1;
            return -1;
        }
        SSL_set_fd(ssl, client->socket);
        SSL_set_tlsext_host_name(ssl, server);
        if (SSL_connect(ssl) <= 0) {
            SSL_free(ssl);
            socket_close_portable(client->socket);
            client->socket = -1;
            if (client->on_status) client->on_status(client, "SSL/TLS handshake failed");
            return -1;
        }
        client->ssl_conn = ssl;
        client->use_ssl = 1;
#endif
    }

    strncpy(client->server, server, sizeof(client->server) - 1);
    client->server[sizeof(client->server) - 1] = '\0';
    client->port = port;
    strncpy(client->nickname, nickname, sizeof(client->nickname) - 1);
    client->nickname[sizeof(client->nickname) - 1] = '\0';
    strncpy(client->current_channel, channel, sizeof(client->current_channel) - 1);
    client->current_channel[sizeof(client->current_channel) - 1] = '\0';

    pthread_mutex_lock(&client->state_lock);
    client->connected = 1;
    client->thread_active = 1;
    client->reconnect_next_try = 0;
    pthread_mutex_unlock(&client->state_lock);

    if (client->on_status) {
        char status[256];
        snprintf(status, sizeof(status), "Connected to %s:%d as %s", server, port, nickname);
        client->on_status(client, status);
    }
    irc_logger_write(&client->logger, "Connected to %s:%d\n", server, port);

    if (pthread_create(&client->listen_thread, NULL, irc_client_listen_thread, client) != 0) {
        set_disconnect_state(client, "Failed to start listen thread");
        socket_close_portable(client->socket);
        client->socket = -1;
        return -1;
    }

    if (irc_send_command(client, "CAP LS 302") != 0) return -1;
    client->cap_sent = 1;

    char nick_cmd[256];
    snprintf(nick_cmd, sizeof(nick_cmd), "NICK %s", client->nickname);
    if (irc_send_command(client, nick_cmd) != 0) return -1;

    char user_cmd[256];
    snprintf(user_cmd, sizeof(user_cmd), "USER %s 0 * :A-Net IRC Client 3.1", client->nickname);
    if (irc_send_command(client, user_cmd) != 0) return -1;

    return 0;
}

int irc_client_disconnect(irc_client_t *client) {
    if (!client) return 0;

    int had_socket = (client->socket >= 0);
    pthread_t listener = client->listen_thread;

    pthread_mutex_lock(&client->state_lock);
    client->connected = 0;
    client->thread_active = 0;
    client->autojoin_pending = 0;
    pthread_mutex_unlock(&client->state_lock);

    if (had_socket) {
        (void)irc_send_raw(client, "QUIT :Disconnecting", 1);
        msleep_int(100);
        socket_shutdown_portable(client->socket);
    }

    if (listener && !pthread_equal(listener, pthread_self())) {
        pthread_join(listener, NULL);
        client->listen_thread = 0;
    }

#ifndef ANET_IRC_NO_OPENSSL
    if (client->ssl_conn) {
        SSL_shutdown((SSL *)client->ssl_conn);
        SSL_free((SSL *)client->ssl_conn);
        client->ssl_conn = NULL;
    }
#endif
    if (client->socket >= 0) {
        socket_close_portable(client->socket);
        client->socket = -1;
    }

    irc_logger_write(&client->logger, "Disconnected\n");
    return 0;
}

void irc_client_enable_reconnect(irc_client_t *client, int enabled) {
    if (!client) return;
    client->reconnect_enabled = enabled ? 1 : 0;
}

int irc_client_seconds_until_reconnect(irc_client_t *client) {
    if (!client || !client->reconnect_enabled || client->connected || client->reconnect_next_try == 0) return 0;
    time_t now = time(NULL);
    if (client->reconnect_next_try <= now) return 0;
    return (int)(client->reconnect_next_try - now);
}

int irc_client_should_attempt_reconnect(irc_client_t *client) {
    if (!client || !client->reconnect_enabled || client->connected) return 0;
    if (!client->last_server[0] || !client->last_nickname[0] || !client->last_channel[0] || client->last_port <= 0) return 0;
    if (client->reconnect_attempts >= client->reconnect_max_attempts) return 0;
    if (client->reconnect_next_try == 0) return 0;
    return time(NULL) >= client->reconnect_next_try;
}

int irc_client_reconnect(irc_client_t *client) {
    if (!client) return -1;
    client->reconnect_attempts++;
    if (client->on_status) {
        char msg[256];
        snprintf(msg, sizeof(msg), "Reconnect attempt %d/%d...", client->reconnect_attempts, client->reconnect_max_attempts);
        client->on_status(client, msg);
    }
    int rc = irc_client_connect(client, client->last_server, client->last_port,
                                client->last_nickname, client->last_channel, client->last_use_ssl);
    if (rc == 0) client->reconnect_attempts = 0;
    else client->reconnect_next_try = time(NULL) + client->reconnect_delay_seconds;
    return rc;
}

void irc_set_message_callback(irc_client_t *client, irc_message_callback callback) { if (client) client->on_message = callback; }
void irc_set_names_callback(irc_client_t *client, irc_names_callback callback) { if (client) client->on_names = callback; }
void irc_set_status_callback(irc_client_t *client, irc_status_callback callback) { if (client) client->on_status = callback; }
void irc_set_ctcp_callback(irc_client_t *client, irc_ctcp_callback callback) { if (client) client->on_ctcp = callback; }

void *irc_client_listen_thread(void *arg) {
    irc_client_t *client = (irc_client_t *)arg;
    if (!client) return NULL;

    char line_buf[IRC_MAX_MESSAGE * 2];
    int line_pos = 0;

    while (client->thread_active && client->socket >= 0) {
        char recv_buf[2048];
        int r = 0;

#ifndef ANET_IRC_NO_OPENSSL
        if (client->use_ssl && client->ssl_conn) {
            r = SSL_read((SSL *)client->ssl_conn, recv_buf, (int)sizeof(recv_buf) - 1);
            if (r <= 0) {
                int ssl_err = SSL_get_error((SSL *)client->ssl_conn, r);
                if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) {
                    msleep_int(20);
                    continue;
                }
                if (ssl_err == SSL_ERROR_ZERO_RETURN) {
                    set_disconnect_state(client, "Disconnected from server");
                    break;
                }
                set_disconnect_state(client, "SSL/TLS read failure");
                break;
            }
        } else
#endif
        {
#ifdef _WIN32
            r = recv(client->socket, recv_buf, (int)sizeof(recv_buf) - 1, 0);
            if (r == SOCKET_ERROR) {
                int e = WSAGetLastError();
                if (e == WSAEWOULDBLOCK || e == WSAETIMEDOUT || e == WSAEINTR) {
                    msleep_int(20);
                    continue;
                }
                set_disconnect_state(client, "Disconnected from server");
                break;
            }
#else
            r = (int)recv(client->socket, recv_buf, sizeof(recv_buf) - 1, 0);
            if (r < 0) {
                if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
                    msleep_int(20);
                    continue;
                }
                set_disconnect_state(client, "Disconnected from server");
                break;
            }
#endif
            if (r == 0) {
                set_disconnect_state(client, "Disconnected from server");
                break;
            }
        }

        recv_buf[r] = '\0';
        irc_logger_write(&client->logger, "[RECV %d bytes] %s\n", r, recv_buf);

        for (int i = 0; i < r; i++) {
            char c = recv_buf[i];
            if (c == '\n') {
                line_buf[line_pos] = '\0';
                if (line_pos > 0 && line_buf[line_pos - 1] == '\r') line_buf[line_pos - 1] = '\0';
                if (line_buf[0]) irc_process_line(client, line_buf);
                line_pos = 0;
            } else if (line_pos < (int)sizeof(line_buf) - 1) {
                line_buf[line_pos++] = c;
            }
        }
    }

    pthread_mutex_lock(&client->state_lock);
    client->thread_active = 0;
    pthread_mutex_unlock(&client->state_lock);
    return NULL;
}

static void irc_process_line(irc_client_t *client, const char *line) {
    if (!client || !line) return;

    if (strncmp(line, "PING", 4) == 0) {
        const char *space = strchr(line, ' ');
        if (space) {
            char pong[IRC_MAX_MESSAGE];
            snprintf(pong, sizeof(pong), "PONG %s", space + 1);
            (void)irc_send_command(client, pong);
        }
        return;
    }

    if (strstr(line, " 433 ") != NULL) {
        irc_handle_433_nick_in_use(client, line);
        if (client->on_status) client->on_status(client, line);
        return;
    }

    if (strstr(line, " 001 ") != NULL) {
        if (!client->welcome_seen) {
            client->welcome_seen = 1;
            client->registered = 1;
            client->reconnect_attempts = 0;
            if (client->cap_sent) {
                (void)irc_send_command(client, "CAP END");
                client->cap_sent = 0;
            }
        }
    }

    if (strstr(line, " 376 ") != NULL || strstr(line, " 422 ") != NULL) {
        client->motd_complete = 1;
        (void)irc_client_try_autojoin(client);
    }

    if (strstr(line, " 477 ") != NULL) {
        client->auth_required = 1;
        client->autojoin_pending = 1;
        if (client->on_status) client->on_status(client, "Authentication required. Use /msg NickServ IDENTIFY <password> then /join #room");
    }

    if (strstr(line, "You are now identified") != NULL || strstr(line, " now identified as ") != NULL) {
        client->auth_required = 0;
        if (client->motd_complete) (void)irc_client_try_autojoin(client);
    }

    if (strstr(line, " JOIN ") && line[0] == ':') {
        const char *bang = strchr(line + 1, '!');
        const char *colon = strrchr(line, ':');
        if (bang && colon && bang > line) {
            char nick[64];
            size_t nick_len = (size_t)(bang - (line + 1));
            if (nick_len >= sizeof(nick)) nick_len = sizeof(nick) - 1;
            memcpy(nick, line + 1, nick_len);
            nick[nick_len] = '\0';
            if (strcmp(nick, client->nickname) == 0) {
                strncpy(client->current_channel, colon + 1, sizeof(client->current_channel) - 1);
                client->current_channel[sizeof(client->current_channel) - 1] = '\0';
                client->autojoin_pending = 0;
            }
        }
    }

    if (strstr(line, " 353 ")) {
        const char *colon = strrchr(line, ':');
        if (colon) irc_parse_names_response(client, colon + 1);
        return;
    }

    if (strstr(line, "PRIVMSG")) {
        irc_parse_privmsg(client, line);
        return;
    }

    if (client->on_status) client->on_status(client, line);
}

static void irc_handle_433_nick_in_use(irc_client_t *client, const char *line) {
    (void)line;
    if (!client || !client->connected) return;
    time_t now = time(NULL);
    int suffix = (int)(now % 1000);
    char base[64];
    strncpy(base, client->nickname, sizeof(base) - 1);
    base[sizeof(base) - 1] = '\0';
    if (strlen(base) > 50) base[50] = '\0';
    char newnick[64];
    snprintf(newnick, sizeof(newnick), "%s_%d", base, suffix);
    strncpy(client->nickname, newnick, sizeof(client->nickname) - 1);
    client->nickname[sizeof(client->nickname) - 1] = '\0';
    char cmd[128];
    snprintf(cmd, sizeof(cmd), "NICK %s", client->nickname);
    irc_logger_write(&client->logger, "433 Nick in use, trying %s\n", client->nickname);
    (void)irc_send_command(client, cmd);
}

static void irc_parse_names_response(irc_client_t *client, const char *names_str) {
    if (!client || !names_str) return;
    irc_user_t users[IRC_MAX_USERS];
    int user_count = 0;
    char *copy = dup_cstr(names_str);
    if (!copy) return;
    char *tok = strtok(copy, " ");
    while (tok && user_count < IRC_MAX_USERS) {
        char mode = ' ';
        if (tok[0] == '@' || tok[0] == '+' || tok[0] == '%' || tok[0] == '&' || tok[0] == '~') {
            mode = tok[0];
            tok++;
        }
        if (tok[0]) {
            strncpy(users[user_count].name, tok, sizeof(users[user_count].name) - 1);
            users[user_count].name[sizeof(users[user_count].name) - 1] = '\0';
            users[user_count].mode = mode;
            user_count++;
        }
        tok = strtok(NULL, " ");
    }
    free(copy);
    if (client->on_names) client->on_names(client, client->current_channel, users, user_count);
}

static void irc_parse_privmsg(irc_client_t *client, const char *line) {
    if (!client || !line) return;

    const char *nick_start = (line[0] == ':') ? line + 1 : line;
    const char *nick_end = strchr(nick_start, '!');
    if (!nick_end) return;

    char nick[64];
    size_t nick_len = (size_t)(nick_end - nick_start);
    if (nick_len >= sizeof(nick)) nick_len = sizeof(nick) - 1;
    memcpy(nick, nick_start, nick_len);
    nick[nick_len] = '\0';

    const char *privmsg = strstr(line, "PRIVMSG");
    if (!privmsg) return;
    const char *target_start = privmsg + 8;
    const char *target_end = strchr(target_start, ' ');
    if (!target_end) return;

    char target[64];
    size_t target_len = (size_t)(target_end - target_start);
    if (target_len >= sizeof(target)) target_len = sizeof(target) - 1;
    memcpy(target, target_start, target_len);
    target[target_len] = '\0';

    const char *msg = strstr(target_end, " :");
    if (!msg) return;
    msg += 2;

    if (msg[0] == '\001') {
        irc_parse_ctcp(client, msg);
        return;
    }

    if (client->on_message) client->on_message(client, target, nick, msg);
    irc_logger_write(&client->logger, "<%s> %s\n", nick, msg);
}

static void irc_parse_ctcp(irc_client_t *client, const char *line) {
    if (!client || !line || line[0] != '\001') return;
    if (client->on_ctcp) client->on_ctcp(client, "", "CTCP", line + 1);
}

void irc_send_ctcp_version(irc_client_t *client, const char *nick) {
    if (!client || !nick || !client->connected) return;
    char msg[256];
    snprintf(msg, sizeof(msg), "PRIVMSG %s :\001VERSION A-Net IRC Client 3.1\001", nick);
    (void)irc_send_command(client, msg);
}

void irc_send_ctcp_time(irc_client_t *client, const char *nick) {
    if (!client || !nick || !client->connected) return;
    time_t now = time(NULL);
    struct tm *tm_info = localtime(&now);
    char timestr[64];
    strftime(timestr, sizeof(timestr), "%a %b %d %H:%M:%S %Y", tm_info);
    char msg[256];
    snprintf(msg, sizeof(msg), "PRIVMSG %s :\001TIME %s\001", nick, timestr);
    (void)irc_send_command(client, msg);
}

void irc_send_ctcp_ping(irc_client_t *client, const char *nick, const char *param) {
    if (!client || !nick || !client->connected) return;
    char msg[256];
    if (param && param[0]) snprintf(msg, sizeof(msg), "PRIVMSG %s :\001PING %s\001", nick, param);
    else snprintf(msg, sizeof(msg), "PRIVMSG %s :\001PING\001", nick);
    (void)irc_send_command(client, msg);
}

int irc_bookmark_add(irc_client_t *client, const char *channel) { (void)client; (void)channel; return -1; }
int irc_bookmark_remove(irc_client_t *client, const char *channel) { (void)client; (void)channel; return -1; }
channel_bookmark_t *irc_bookmark_get(irc_client_t *client, int index) { (void)client; (void)index; return NULL; }
char *irc_tab_complete(irc_client_t *client, const char *prefix, int index) { (void)client; (void)prefix; (void)index; return NULL; }
void irc_add_to_history(irc_client_t *client, const char *command) { (void)client; (void)command; }
