package Hypersonic::Socket;

use strict;
use warnings;
use 5.010;

our $VERSION = '0.02';

use XS::JIT;
use XS::JIT::Builder;
use Config;

# Platform detection
sub platform {
    return 'darwin'  if $^O eq 'darwin';
    return 'linux'   if $^O eq 'linux';
    return 'freebsd' if $^O eq 'freebsd';
    return 'openbsd' if $^O eq 'openbsd';
    return 'netbsd'  if $^O eq 'netbsd';
    die "Unsupported platform: $^O";
}

sub event_backend {
    my $p = platform();
    return 'kqueue' if $p =~ /^(darwin|freebsd|openbsd|netbsd)$/;
    return 'epoll'  if $p eq 'linux';
    die "No event backend for $p";
}

# Generate C code for the ops module - all functions are XS-native
sub generate_ops_code {
    my $backend = event_backend();

    my @lines;

    # Platform-specific includes
    push @lines, '#include <stdio.h>';
    push @lines, '#include <stdlib.h>';
    push @lines, '#include <string.h>';
    push @lines, '#include <unistd.h>';
    push @lines, '#include <fcntl.h>';
    push @lines, '#include <errno.h>';
    push @lines, '#include <sys/socket.h>';
    push @lines, '#include <sys/types.h>';
    push @lines, '#include <netinet/in.h>';
    push @lines, '#include <arpa/inet.h>';
    push @lines, '#include <sys/uio.h>';
    push @lines, '';

    if ($backend eq 'epoll') {
        push @lines, '#include <sys/epoll.h>';
        push @lines, '#define USE_EPOLL 1';
    } else {
        push @lines, '#include <sys/event.h>';
        push @lines, '#define USE_KQUEUE 1';
    }
    push @lines, '';

    push @lines, '#define MAX_EVENTS 1024';
    push @lines, '#define RECV_BUF_SIZE 65536';
    push @lines, '';
    push @lines, 'static char recv_buf[RECV_BUF_SIZE];';
    push @lines, '';

    # All functions use XS-native style
    push @lines, _gen_create_listen_socket_xs();
    push @lines, '';

    if ($backend eq 'epoll') {
        push @lines, _gen_create_event_loop_epoll_xs();
        push @lines, '';
        push @lines, _gen_event_add_epoll_xs();
        push @lines, '';
        push @lines, _gen_event_del_epoll_xs();
        push @lines, '';
        push @lines, _gen_ev_poll_epoll_xs();
    } else {
        push @lines, _gen_create_event_loop_kqueue_xs();
        push @lines, '';
        push @lines, _gen_event_add_kqueue_xs();
        push @lines, '';
        push @lines, _gen_event_del_kqueue_xs();
        push @lines, '';
        push @lines, _gen_ev_poll_kqueue_xs();
    }
    push @lines, '';

    push @lines, _gen_http_accept_xs();
    push @lines, '';

    push @lines, _gen_http_recv_xs();
    push @lines, '';

    push @lines, _gen_http_send_xs();
    push @lines, '';

    push @lines, _gen_http_send_404_xs();
    push @lines, '';

    push @lines, _gen_close_fd_xs();

    return join("\n", @lines);
}

# XS-native functions (is_xs_native => 1)

sub _gen_create_listen_socket_xs {
    return <<'C';
XS_EUPXS(hypersonic_create_listen_socket) {
    dVAR; dXSARGS;
    if (items != 1) croak_xs_usage(cv, "port");

    IV port = SvIV(ST(0));

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        ST(0) = sv_2mortal(newSViv(-1));
        XSRETURN(1);
    }

    int opt = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#ifdef SO_REUSEPORT
    setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
#endif

    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons((uint16_t)port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        close(fd);
        ST(0) = sv_2mortal(newSViv(-1));
        XSRETURN(1);
    }

    if (listen(fd, SOMAXCONN) < 0) {
        close(fd);
        ST(0) = sv_2mortal(newSViv(-1));
        XSRETURN(1);
    }

    ST(0) = sv_2mortal(newSViv(fd));
    XSRETURN(1);
}
C
}

sub _gen_create_event_loop_epoll_xs {
    return <<'C';
XS_EUPXS(hypersonic_create_event_loop) {
    dVAR; dXSARGS;
    if (items != 1) croak_xs_usage(cv, "listen_fd");

    IV listen_fd = SvIV(ST(0));

    int epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        ST(0) = sv_2mortal(newSViv(-1));
        XSRETURN(1);
    }

    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = (int)listen_fd;

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, (int)listen_fd, &ev) < 0) {
        close(epoll_fd);
        ST(0) = sv_2mortal(newSViv(-1));
        XSRETURN(1);
    }

    ST(0) = sv_2mortal(newSViv(epoll_fd));
    XSRETURN(1);
}
C
}

sub _gen_create_event_loop_kqueue_xs {
    return <<'C';
XS_EUPXS(hypersonic_create_event_loop) {
    dVAR; dXSARGS;
    if (items != 1) croak_xs_usage(cv, "listen_fd");

    IV listen_fd = SvIV(ST(0));

    int kq = kqueue();
    if (kq < 0) {
        ST(0) = sv_2mortal(newSViv(-1));
        XSRETURN(1);
    }

    struct kevent ev;
    EV_SET(&ev, listen_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);

    if (kevent(kq, &ev, 1, NULL, 0, NULL) < 0) {
        close(kq);
        ST(0) = sv_2mortal(newSViv(-1));
        XSRETURN(1);
    }

    ST(0) = sv_2mortal(newSViv(kq));
    XSRETURN(1);
}
C
}

sub _gen_event_add_epoll_xs {
    return <<'C';
XS_EUPXS(hypersonic_event_add) {
    dVAR; dXSARGS;
    if (items != 2) croak_xs_usage(cv, "loop_fd, fd");

    IV loop_fd = SvIV(ST(0));
    IV fd = SvIV(ST(1));

    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = (int)fd;

    int result = epoll_ctl((int)loop_fd, EPOLL_CTL_ADD, (int)fd, &ev);
    ST(0) = sv_2mortal(newSViv(result));
    XSRETURN(1);
}
C
}

sub _gen_event_add_kqueue_xs {
    return <<'C';
XS_EUPXS(hypersonic_event_add) {
    dVAR; dXSARGS;
    if (items != 2) croak_xs_usage(cv, "loop_fd, fd");

    IV loop_fd = SvIV(ST(0));
    IV fd = SvIV(ST(1));

    struct kevent ev;
    EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);

    int result = kevent((int)loop_fd, &ev, 1, NULL, 0, NULL);
    ST(0) = sv_2mortal(newSViv(result));
    XSRETURN(1);
}
C
}

sub _gen_event_del_epoll_xs {
    return <<'C';
XS_EUPXS(hypersonic_event_del) {
    dVAR; dXSARGS;
    if (items != 2) croak_xs_usage(cv, "loop_fd, fd");

    IV loop_fd = SvIV(ST(0));
    IV fd = SvIV(ST(1));

    int result = epoll_ctl((int)loop_fd, EPOLL_CTL_DEL, (int)fd, NULL);
    ST(0) = sv_2mortal(newSViv(result));
    XSRETURN(1);
}
C
}

sub _gen_event_del_kqueue_xs {
    return <<'C';
XS_EUPXS(hypersonic_event_del) {
    dVAR; dXSARGS;
    if (items != 2) croak_xs_usage(cv, "loop_fd, fd");

    IV loop_fd = SvIV(ST(0));
    IV fd = SvIV(ST(1));

    struct kevent ev;
    EV_SET(&ev, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);

    int result = kevent((int)loop_fd, &ev, 1, NULL, 0, NULL);
    ST(0) = sv_2mortal(newSViv(result));
    XSRETURN(1);
}
C
}

sub _gen_ev_poll_epoll_xs {
    return <<'C';
XS_EUPXS(hypersonic_ev_poll) {
    dVAR; dXSARGS;
    if (items != 2) croak_xs_usage(cv, "loop_fd, timeout_ms");

    IV loop_fd = SvIV(ST(0));
    IV timeout_ms = SvIV(ST(1));

    struct epoll_event events[MAX_EVENTS];
    int n = epoll_wait((int)loop_fd, events, MAX_EVENTS, (int)timeout_ms);

    if (n < 0) {
        ST(0) = &PL_sv_undef;
        XSRETURN(1);
    }

    AV* ready = newAV();
    int i;
    for (i = 0; i < n; i++) {
        AV* event = newAV();
        av_push(event, newSViv(events[i].data.fd));
        av_push(event, newSViv(events[i].events));
        av_push(ready, newRV_noinc((SV*)event));
    }

    ST(0) = sv_2mortal(newRV_noinc((SV*)ready));
    XSRETURN(1);
}
C
}

sub _gen_ev_poll_kqueue_xs {
    return <<'C';
XS_EUPXS(hypersonic_ev_poll) {
    dVAR; dXSARGS;
    if (items != 2) croak_xs_usage(cv, "loop_fd, timeout_ms");

    IV loop_fd = SvIV(ST(0));
    IV timeout_ms = SvIV(ST(1));

    struct kevent events[MAX_EVENTS];
    struct timespec ts;
    ts.tv_sec = timeout_ms / 1000;
    ts.tv_nsec = (timeout_ms % 1000) * 1000000;

    int n = kevent((int)loop_fd, NULL, 0, events, MAX_EVENTS, &ts);

    if (n < 0) {
        ST(0) = &PL_sv_undef;
        XSRETURN(1);
    }

    AV* ready = newAV();
    int i;
    for (i = 0; i < n; i++) {
        AV* event = newAV();
        av_push(event, newSViv((IV)events[i].ident));
        av_push(event, newSViv(events[i].filter == EVFILT_READ ? 1 : 2));
        av_push(ready, newRV_noinc((SV*)event));
    }

    ST(0) = sv_2mortal(newRV_noinc((SV*)ready));
    XSRETURN(1);
}
C
}

sub _gen_http_accept_xs {
    return <<'C';
XS_EUPXS(hypersonic_http_accept) {
    dVAR; dXSARGS;
    if (items != 1) croak_xs_usage(cv, "listen_fd");

    IV listen_fd = SvIV(ST(0));

    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    int client_fd = accept((int)listen_fd, (struct sockaddr*)&client_addr, &client_len);

    if (client_fd < 0) {
        ST(0) = sv_2mortal(newSViv(-1));
        XSRETURN(1);
    }

    int flags = fcntl(client_fd, F_GETFL, 0);
    fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);

    ST(0) = sv_2mortal(newSViv(client_fd));
    XSRETURN(1);
}
C
}

sub _gen_http_recv_xs {
    return <<'C';
XS_EUPXS(hypersonic_http_recv) {
    dVAR; dXSARGS;
    if (items != 1) croak_xs_usage(cv, "fd");

    IV fd = SvIV(ST(0));

    ssize_t len = recv((int)fd, recv_buf, RECV_BUF_SIZE - 1, 0);

    if (len <= 0) {
        ST(0) = &PL_sv_undef;
        XSRETURN(1);
    }

    recv_buf[len] = '\0';

    /* Quick parse - extract method, path, detect keep-alive */
    const char* p = recv_buf;
    const char* end = recv_buf + len;

    /* Method */
    const char* method = p;
    while (p < end && *p != ' ') p++;
    int method_len = p - method;
    if (p >= end) { ST(0) = &PL_sv_undef; XSRETURN(1); }
    p++;

    /* Path */
    const char* path = p;
    while (p < end && *p != ' ' && *p != '?') p++;
    int path_len = p - path;
    if (p >= end) { ST(0) = &PL_sv_undef; XSRETURN(1); }

    /* Skip to end of request line */
    while (p < end && *p != '\n') p++;
    if (p >= end) { ST(0) = &PL_sv_undef; XSRETURN(1); }
    p++;

    /* Check for Connection: close */
    int keep_alive = 1;
    while (p < end) {
        if (*p == '\r' || *p == '\n') break;

        if (end - p > 17 && strncasecmp(p, "Connection: close", 17) == 0) {
            keep_alive = 0;
        }

        while (p < end && *p != '\n') p++;
        if (p < end) p++;
    }

    /* Skip blank line */
    if (p < end && *p == '\r') p++;
    if (p < end && *p == '\n') p++;

    /* Body */
    const char* body = p;
    int body_len = end - p;

    /* Build request array: [method, path, body, keep_alive, fd] */
    AV* req = newAV();
    av_push(req, newSVpvn(method, method_len));
    av_push(req, newSVpvn(path, path_len));
    av_push(req, newSVpvn(body, body_len));
    av_push(req, newSViv(keep_alive));
    av_push(req, newSViv(fd));

    ST(0) = sv_2mortal(newRV_noinc((SV*)req));
    XSRETURN(1);
}
C
}

sub _gen_http_send_xs {
    return <<'C';
XS_EUPXS(hypersonic_http_send) {
    dVAR; dXSARGS;
    if (items < 2 || items > 3) croak_xs_usage(cv, "fd, body, [content_type]");

    IV fd = SvIV(ST(0));

    STRLEN body_len;
    const char* body = SvPV(ST(1), body_len);

    const char* content_type = "text/plain";
    if (items == 3 && SvOK(ST(2))) {
        STRLEN ct_len;
        content_type = SvPV(ST(2), ct_len);
    }

    static char header[512];
    int hdr_len = snprintf(header, sizeof(header),
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %zu\r\n"
        "Connection: keep-alive\r\n\r\n",
        content_type, body_len);

    struct iovec iov[2];
    iov[0].iov_base = header;
    iov[0].iov_len = (size_t)hdr_len;
    iov[1].iov_base = (void*)body;
    iov[1].iov_len = body_len;

    ssize_t sent = writev((int)fd, iov, 2);
    ST(0) = sv_2mortal(newSViv((IV)sent));
    XSRETURN(1);
}
C
}

sub _gen_http_send_404_xs {
    return <<'C';
XS_EUPXS(hypersonic_http_send_404) {
    dVAR; dXSARGS;
    if (items != 1) croak_xs_usage(cv, "fd");

    IV fd = SvIV(ST(0));

    static const char resp[] =
        "HTTP/1.1 404 Not Found\r\n"
        "Content-Type: text/plain\r\n"
        "Content-Length: 9\r\n"
        "Connection: close\r\n\r\n"
        "Not Found";

    ssize_t sent = send((int)fd, resp, sizeof(resp) - 1, 0);
    ST(0) = sv_2mortal(newSViv((IV)sent));
    XSRETURN(1);
}
C
}

sub _gen_close_fd_xs {
    return <<'C';
XS_EUPXS(hypersonic_close_fd) {
    dVAR; dXSARGS;
    if (items != 1) croak_xs_usage(cv, "fd");

    IV fd = SvIV(ST(0));
    int result = close((int)fd);
    ST(0) = sv_2mortal(newSViv(result));
    XSRETURN(1);
}
C
}

# Compile and install the ops module
{
    my $compiled = 0;

    sub _ensure_compiled {
        return if $compiled;

        my $code = generate_ops_code();

        XS::JIT->compile(
            code      => $code,
            name      => 'Hypersonic::Socket::_Native',
            cache_dir => '_hypersonic_cache',
            functions => {
                'Hypersonic::Socket::create_listen_socket' => { source => 'hypersonic_create_listen_socket', is_xs_native => 1 },
                'Hypersonic::Socket::create_event_loop'    => { source => 'hypersonic_create_event_loop', is_xs_native => 1 },
                'Hypersonic::Socket::event_add'            => { source => 'hypersonic_event_add', is_xs_native => 1 },
                'Hypersonic::Socket::event_del'            => { source => 'hypersonic_event_del', is_xs_native => 1 },
                'Hypersonic::Socket::ev_poll'              => { source => 'hypersonic_ev_poll', is_xs_native => 1 },
                'Hypersonic::Socket::http_accept'          => { source => 'hypersonic_http_accept', is_xs_native => 1 },
                'Hypersonic::Socket::http_recv'            => { source => 'hypersonic_http_recv', is_xs_native => 1 },
                'Hypersonic::Socket::http_send'            => { source => 'hypersonic_http_send', is_xs_native => 1 },
                'Hypersonic::Socket::http_send_404'        => { source => 'hypersonic_http_send_404', is_xs_native => 1 },
                'Hypersonic::Socket::close_fd'             => { source => 'hypersonic_close_fd', is_xs_native => 1 },
            },
        );

        $compiled = 1;
    }
}

# Auto-compile on import
sub import {
    my $class = shift;
    _ensure_compiled();
}

1;

__END__

=head1 NAME

Hypersonic::Socket - Low-level JIT-compiled socket functions for Hypersonic

=head1 SYNOPSIS

    use Hypersonic::Socket;

    my $listen_fd = Hypersonic::Socket::create_listen_socket(8080);
    my $loop_fd   = Hypersonic::Socket::create_event_loop($listen_fd);

    while (1) {
        my $events = Hypersonic::Socket::ev_poll($loop_fd, 1000);
        for my $event (@$events) {
            my ($fd, $flags) = @$event;
            # Handle event...
        }
    }

=head1 DESCRIPTION

This module provides low-level XS functions for Hypersonic, compiled at
runtime via XS::JIT. Platform-specific C code (kqueue on macOS/BSD,
epoll on Linux) is generated and compiled on first use.

B<Note:> These are XS functions, not Perl custom ops.

=head1 FUNCTIONS

=head2 create_listen_socket($port)

Create a non-blocking listening socket on the given port.

=head2 create_event_loop($listen_fd)

Create an epoll (Linux) or kqueue (macOS/BSD) event loop.

=head2 event_add($loop_fd, $fd)

Add a file descriptor to the event loop.

=head2 event_del($loop_fd, $fd)

Remove a file descriptor from the event loop.

=head2 ev_poll($loop_fd, $timeout_ms)

Wait for events. Returns an arrayref of C<[$fd, $flags]> pairs.

=head2 http_accept($listen_fd)

Accept a new connection. Returns client fd or -1.

=head2 http_recv($fd)

Receive and parse an HTTP request. Returns C<[method, path, body, keep_alive, fd]>.

=head2 http_send($fd, $body, $content_type)

Send an HTTP response with the given body.

=head2 http_send_404($fd)

Send a 404 Not Found response.

=cut
