package Hypersonic;

use strict;
use warnings;
use 5.010;

our $VERSION = '0.02';

use XS::JIT;
use XS::JIT::Builder;
use Hypersonic::Socket;

sub new {
    my ($class, %opts) = @_;
    return bless {
        routes    => [],
        compiled  => 0,
        cache_dir => $opts{cache_dir} // '_hypersonic_cache',
        id        => int(rand(100000)),
    }, $class;
}

# Route registration methods
sub get    { shift->_add_route('GET',    @_) }
sub post   { shift->_add_route('POST',   @_) }
sub put    { shift->_add_route('PUT',    @_) }
sub del    { shift->_add_route('DELETE', @_) }
sub patch  { shift->_add_route('PATCH',  @_) }
sub head   { shift->_add_route('HEAD',   @_) }
sub options { shift->_add_route('OPTIONS', @_) }

sub _add_route {
    my ($self, $method, $path, $handler, $opts) = @_;

    die "Path must start with /" unless $path =~ m{^/};
    die "Handler must be a code ref" unless ref($handler) eq 'CODE';

    # Check for dynamic option
    my $dynamic = 0;
    if (ref($opts) eq 'HASH') {
        $dynamic = $opts->{dynamic} ? 1 : 0;
    }

    # Check for path parameters (implies dynamic)
    if ($path =~ /:(\w+)/) {
        $dynamic = 1;
    }

    push @{$self->{routes}}, {
        method  => $method,
        path    => $path,
        handler => $handler,
        dynamic => $dynamic,
    };

    return $self;
}

sub compile {
    my ($self) = @_;

    die "No routes defined" unless @{$self->{routes}};
    die "Already compiled" if $self->{compiled};

    # Pre-evaluate all static handlers and build FULL HTTP responses
    my @full_responses;
    my @dynamic_handlers;  # Store CV refs for dynamic routes

    for my $i (0 .. $#{$self->{routes}}) {
        my $route = $self->{routes}[$i];

        if (!$route->{dynamic}) {
            # RUN THE HANDLER ONCE - this is the magic
            my $body = $route->{handler}->();
            die "Handler for $route->{method} $route->{path} must return a string"
                unless defined $body && !ref($body);

            # Determine content type
            my $ct = ($body =~ /^\s*[\[{]/) ? 'application/json' : 'text/plain';

            # Build COMPLETE HTTP response at compile time
            my $full_response = "HTTP/1.1 200 OK\r\n"
                              . "Content-Type: $ct\r\n"
                              . "Content-Length: " . length($body) . "\r\n"
                              . "Connection: keep-alive\r\n"
                              . "\r\n"
                              . $body;

            push @full_responses, $full_response;
            $route->{response_idx} = $#full_responses;
        } else {
            # Dynamic route - store handler index
            push @dynamic_handlers, $route->{handler};
            $route->{handler_idx} = $#dynamic_handlers;
        }
    }

    # Store dynamic handlers for runtime access
    $self->{dynamic_handlers} = \@dynamic_handlers;

    # Generate C code with pure C event loop
    my $c_code = $self->_generate_server_code(\@full_responses);

    # Compile via XS::JIT
    my $module_name = 'Hypersonic::_Server_' . $self->{id};

    XS::JIT->compile(
        code      => $c_code,
        name      => $module_name,
        cache_dir => $self->{cache_dir},
        functions => {
            "${module_name}::run_event_loop" => {
                source       => 'hypersonic_run_event_loop',
                is_xs_native => 1,
            },
            "${module_name}::dispatch" => {
                source       => 'hypersonic_dispatch',
                is_xs_native => 1,
            },
        },
    );

    # Store function references
    {
        no strict 'refs';
        $self->{run_loop_fn} = \&{"${module_name}::run_event_loop"};
        $self->{dispatch_fn} = \&{"${module_name}::dispatch"};
    }

    $self->{compiled} = 1;
    return $self;
}

sub _generate_server_code {
    my ($self, $full_responses) = @_;

    my $backend = Hypersonic::Socket::event_backend();
    my @lines;

    # Check if we have any dynamic routes
    my $has_dynamic = grep { $_->{dynamic} } @{$self->{routes}};

    # Includes
    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 <netinet/tcp.h>';
    if ($backend eq 'kqueue') {
        push @lines, '#include <sys/event.h>';
    } else {
        push @lines, '#include <sys/epoll.h>';
    }
    push @lines, '';
    push @lines, '#define MAX_EVENTS 1024';
    push @lines, '#define RECV_BUF_SIZE 8192';
    push @lines, '';

    # Global storage for dynamic handler dispatch (always emit, may not be used)
    push @lines, '/* Storage for dynamic handler callbacks */';
    push @lines, 'static SV* g_handler_array = NULL;';
    push @lines, 'static SV* g_server_obj = NULL;';
    push @lines, '';

    # Emit FULL pre-computed HTTP responses (headers + body)
    for my $i (0 .. $#$full_responses) {
        my $resp = $full_responses->[$i];
        my $escaped = _escape_c_string($resp);
        my $len = length($resp);
        push @lines, "static const char RESP_$i\[] = \"$escaped\";";
        push @lines, "static const int RESP_${i}_LEN = $len;";
    }
    push @lines, '';

    # 404 response
    my $resp_404 = "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";
    my $escaped_404 = _escape_c_string($resp_404);
    push @lines, "static const char RESP_404[] = \"$escaped_404\";";
    push @lines, "static const int RESP_404_LEN = " . length($resp_404) . ";";
    push @lines, '';

    # Generate dynamic handler caller if needed
    if ($has_dynamic) {
        push @lines, $self->_gen_dynamic_handler_caller();
        push @lines, '';
    }

    # Group routes by method for dispatch generation
    my %methods;
    for my $route (@{$self->{routes}}) {
        push @{$methods{$route->{method}}}, $route;
    }

    # Generate inline C dispatch function
    # For dynamic routes, returns handler_idx + 1000 to signal dynamic dispatch needed
    push @lines, '/* Inline dispatch - returns response pointer and length */';
    push @lines, '/* For dynamic routes: returns handler_idx in *handler_idx_out, sets *resp_out to NULL */';
    push @lines, 'static inline int dispatch_request(const char* method, int method_len, const char* path, int path_len, const char** resp_out, int* resp_len_out, int* handler_idx_out) {';
    push @lines, '    *handler_idx_out = -1;';

    for my $method (sort keys %methods) {
        my $mlen = length($method);
        push @lines, "    if (method_len == $mlen && memcmp(method, \"$method\", $mlen) == 0) {";

        for my $r (@{$methods{$method}}) {
            my $path = $r->{path};
            my $plen = length($path);

            if (!$r->{dynamic}) {
                # Static route - return pre-computed response
                my $escaped_path = _escape_c_string($path);
                my $idx = $r->{response_idx};

                push @lines, "        if (path_len == $plen && memcmp(path, \"$escaped_path\", $plen) == 0) {";
                push @lines, "            *resp_out = RESP_$idx;";
                push @lines, "            *resp_len_out = RESP_${idx}_LEN;";
                push @lines, "            return 0;";
                push @lines, "        }";
            } else {
                # Dynamic route - check for path params or exact match
                my $handler_idx = $r->{handler_idx};

                if ($path =~ /:(\w+)/) {
                    # Path has parameters - generate pattern matching
                    my $pattern = $path;
                    my @params;
                    while ($pattern =~ /:(\w+)/g) {
                        push @params, $1;
                    }
                    # Convert :param to regex-like matching
                    # For now, simple prefix match for paths like /users/:id
                    my ($prefix) = $path =~ m{^([^:]+)};
                    my $prefix_len = length($prefix);
                    my $escaped_prefix = _escape_c_string($prefix);

                    push @lines, "        if (path_len >= $prefix_len && memcmp(path, \"$escaped_prefix\", $prefix_len) == 0) {";
                    push @lines, "            *resp_out = NULL;";
                    push @lines, "            *handler_idx_out = $handler_idx;";
                    push @lines, "            return 1; /* dynamic */";
                    push @lines, "        }";
                } else {
                    # Exact match dynamic route
                    my $escaped_path = _escape_c_string($path);
                    push @lines, "        if (path_len == $plen && memcmp(path, \"$escaped_path\", $plen) == 0) {";
                    push @lines, "            *resp_out = NULL;";
                    push @lines, "            *handler_idx_out = $handler_idx;";
                    push @lines, "            return 1; /* dynamic */";
                    push @lines, "        }";
                }
            }
        }

        push @lines, "    }";
    }

    push @lines, '    *resp_out = RESP_404;';
    push @lines, '    *resp_len_out = RESP_404_LEN;';
    push @lines, '    return -1;';
    push @lines, '}';
    push @lines, '';

    # Generate the pure C event loop
    if ($backend eq 'kqueue') {
        push @lines, $self->_gen_event_loop_kqueue();
    } else {
        push @lines, $self->_gen_event_loop_epoll();
    }
    push @lines, '';

    # Keep the Perl-callable dispatch for testing/compatibility
    push @lines, '/* Perl-callable dispatch wrapper */';
    push @lines, 'XS_EUPXS(hypersonic_dispatch) {';
    push @lines, '    dVAR; dXSARGS;';
    push @lines, '    PERL_UNUSED_VAR(cv);';
    push @lines, '    if (items != 1) croak_xs_usage(cv, "req_ref");';
    push @lines, '    SV* req_ref = ST(0);';
    push @lines, '    if (!SvROK(req_ref) || SvTYPE(SvRV(req_ref)) != SVt_PVAV) {';
    push @lines, '        ST(0) = &PL_sv_undef;';
    push @lines, '        XSRETURN(1);';
    push @lines, '    }';
    push @lines, '    AV* req = (AV*)SvRV(req_ref);';
    push @lines, '    SV** ary = AvARRAY(req);';
    push @lines, '    STRLEN method_len, path_len;';
    push @lines, '    const char* method = SvPV(ary[0], method_len);';
    push @lines, '    const char* path = SvPV(ary[1], path_len);';
    push @lines, '    const char* resp;';
    push @lines, '    int resp_len;';
    push @lines, '    int handler_idx;';
    push @lines, '    int rc = dispatch_request(method, (int)method_len, path, (int)path_len, &resp, &resp_len, &handler_idx);';
    push @lines, '    if (rc == 0) {';
    push @lines, '        /* Static route */';
    push @lines, '        ST(0) = sv_2mortal(newSVpvn(resp, resp_len));';
    push @lines, '    } else if (rc == 1) {';
    push @lines, '        /* Dynamic route - for testing, just return undef */';
    push @lines, '        /* Full dynamic dispatch only works in event loop */';
    push @lines, '        ST(0) = &PL_sv_undef;';
    push @lines, '    } else {';
    push @lines, '        ST(0) = &PL_sv_undef;';
    push @lines, '    }';
    push @lines, '    XSRETURN(1);';
    push @lines, '}';

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

sub _gen_event_loop_kqueue {
    my ($self) = @_;
    my $has_dynamic = grep { $_->{dynamic} } @{$self->{routes}};

    my $dynamic_dispatch = '';
    if ($has_dynamic) {
        $dynamic_dispatch = <<'C';
                if (dispatch_result == 1) {
                    /* Dynamic route - call Perl handler */
                    /* Find body in request */
                    const char* body_start = strstr(recv_buf, "\r\n\r\n");
                    const char* body = "";
                    int body_len = 0;
                    if (body_start) {
                        body = body_start + 4;
                        body_len = len - (body - recv_buf);
                    }

                    char* dyn_resp;
                    int dyn_resp_len;
                    call_dynamic_handler(aTHX_ handler_idx, method, method_len,
                                        path, path_len, body, body_len,
                                        &dyn_resp, &dyn_resp_len);
                    send(fd, dyn_resp, dyn_resp_len, 0);
                } else {
                    send(fd, resp, resp_len, 0);
                }
C
    } else {
        $dynamic_dispatch = '                send(fd, resp, resp_len, 0);';
    }

    return <<"C";
/* Pure C event loop using kqueue - OPTIMIZED */
XS_EUPXS(hypersonic_run_event_loop) {
    dVAR; dXSARGS;
    PERL_UNUSED_VAR(cv);
    if (items != 2) croak_xs_usage(cv, "listen_fd, server_obj");

    int listen_fd = (int)SvIV(ST(0));
    SV* server_obj = ST(1);

    /* Store server object for dynamic handler access */
    if (SvROK(server_obj)) {
        HV* self = (HV*)SvRV(server_obj);
        SV** handlers_ref = hv_fetch(self, "dynamic_handlers", 16, 0);
        if (handlers_ref && SvROK(*handlers_ref)) {
            g_handler_array = *handlers_ref;
            SvREFCNT_inc(g_handler_array);
        }
        g_server_obj = server_obj;
        SvREFCNT_inc(g_server_obj);
    }

    /* Thread-local receive buffer - each worker gets its own */
    static __thread char recv_buf[RECV_BUF_SIZE];

    int kq = kqueue();
    if (kq < 0) {
        croak("kqueue() failed");
    }

    struct kevent ev;

    /* Add listen socket */
    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);
        croak("kevent() failed to add listen socket");
    }

    struct kevent events[MAX_EVENTS];

    while (1) {
        int n = kevent(kq, NULL, 0, events, MAX_EVENTS, NULL);

        if (n < 0) {
            if (errno == EINTR) continue;
            break;
        }

        int i;
        for (i = 0; i < n; i++) {
            int fd = (int)events[i].ident;

            if (fd == listen_fd) {
                /* Accept new connections */
                while (1) {
                    struct sockaddr_in client_addr;
                    socklen_t client_len = sizeof(client_addr);
                    int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
                    if (client_fd < 0) break;

                    /* Set non-blocking */
                    int flags = fcntl(client_fd, F_GETFL, 0);
                    fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);

                    /* Disable Nagle */
                    int one = 1;
                    setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));

                    /* Add to kqueue */
                    EV_SET(&ev, client_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
                    kevent(kq, &ev, 1, NULL, 0, NULL);
                }
            } else {
                /* Handle client request */
                ssize_t len = recv(fd, recv_buf, RECV_BUF_SIZE - 1, 0);

                if (len <= 0) {
                    /* Connection closed */
                    EV_SET(&ev, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
                    kevent(kq, &ev, 1, NULL, 0, NULL);
                    close(fd);
                    continue;
                }

                recv_buf[len] = '\\0';

                /* Quick parse - method and path */
                const char* p = recv_buf;
                const char* method = p;
                while (*p && *p != ' ') p++;
                int method_len = p - method;
                if (*p) p++;

                const char* path = p;
                while (*p && *p != ' ' && *p != '?') p++;
                int path_len = p - path;

                /* Dispatch request */
                const char* resp;
                int resp_len;
                int handler_idx;
                int dispatch_result = dispatch_request(method, method_len, path, path_len, &resp, &resp_len, &handler_idx);

$dynamic_dispatch

                /* Check for Connection: close */
                int keep_alive = 1;
                if (len > 20) {
                    const char* conn = strstr(recv_buf + 16, "onnection:");
                    if (conn && (conn[-1] == 'C' || conn[-1] == 'c')) {
                        if (strstr(conn, "close") || strstr(conn, "Close")) {
                            keep_alive = 0;
                        }
                    }
                }

                if (!keep_alive) {
                    EV_SET(&ev, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
                    kevent(kq, &ev, 1, NULL, 0, NULL);
                    close(fd);
                }
            }
        }
    }

    close(kq);
    XSRETURN(0);
}
C
}

sub _gen_event_loop_epoll {
    my ($self) = @_;
    my $has_dynamic = grep { $_->{dynamic} } @{$self->{routes}};

    my $dynamic_dispatch = '';
    if ($has_dynamic) {
        $dynamic_dispatch = <<'C';
                if (dispatch_result == 1) {
                    /* Dynamic route - call Perl handler */
                    const char* body_start = strstr(recv_buf, "\r\n\r\n");
                    const char* body = "";
                    int body_len = 0;
                    if (body_start) {
                        body = body_start + 4;
                        body_len = len - (body - recv_buf);
                    }

                    char* dyn_resp;
                    int dyn_resp_len;
                    call_dynamic_handler(aTHX_ handler_idx, method, method_len,
                                        path, path_len, body, body_len,
                                        &dyn_resp, &dyn_resp_len);
                    send(fd, dyn_resp, dyn_resp_len, 0);
                } else {
                    send(fd, resp, resp_len, 0);
                }
C
    } else {
        $dynamic_dispatch = '                send(fd, resp, resp_len, 0);';
    }

    return <<"C";
/* Pure C event loop using epoll - OPTIMIZED */
XS_EUPXS(hypersonic_run_event_loop) {
    dVAR; dXSARGS;
    PERL_UNUSED_VAR(cv);
    if (items != 2) croak_xs_usage(cv, "listen_fd, server_obj");

    int listen_fd = (int)SvIV(ST(0));
    SV* server_obj = ST(1);

    /* Store server object for dynamic handler access */
    if (SvROK(server_obj)) {
        HV* self = (HV*)SvRV(server_obj);
        SV** handlers_ref = hv_fetch(self, "dynamic_handlers", 16, 0);
        if (handlers_ref && SvROK(*handlers_ref)) {
            g_handler_array = *handlers_ref;
            SvREFCNT_inc(g_handler_array);
        }
        g_server_obj = server_obj;
        SvREFCNT_inc(g_server_obj);
    }

    /* Thread-local receive buffer */
    static __thread char recv_buf[RECV_BUF_SIZE];

    int epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        croak("epoll_create1() failed");
    }

    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listen_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) < 0) {
        close(epoll_fd);
        croak("epoll_ctl() failed to add listen socket");
    }

    struct epoll_event events[MAX_EVENTS];

    while (1) {
        int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (n < 0) {
            if (errno == EINTR) continue;
            break;
        }

        int i;
        for (i = 0; i < n; i++) {
            int fd = events[i].data.fd;

            if (fd == listen_fd) {
                /* Accept new connections */
                while (1) {
                    struct sockaddr_in client_addr;
                    socklen_t client_len = sizeof(client_addr);
                    int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
                    if (client_fd < 0) break;

                    /* Set non-blocking */
                    int flags = fcntl(client_fd, F_GETFL, 0);
                    fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);

                    /* Disable Nagle */
                    int one = 1;
                    setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));

                    /* Add to epoll */
                    ev.events = EPOLLIN | EPOLLET;
                    ev.data.fd = client_fd;
                    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
                }
            } else {
                /* Handle client request */
                ssize_t len = recv(fd, recv_buf, RECV_BUF_SIZE - 1, 0);

                if (len <= 0) {
                    /* Connection closed */
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
                    close(fd);
                    continue;
                }

                recv_buf[len] = '\\0';

                /* Quick parse - method and path */
                const char* p = recv_buf;
                const char* method = p;
                while (*p && *p != ' ') p++;
                int method_len = p - method;
                if (*p) p++;

                const char* path = p;
                while (*p && *p != ' ' && *p != '?') p++;
                int path_len = p - path;

                /* Dispatch request */
                const char* resp;
                int resp_len;
                int handler_idx;
                int dispatch_result = dispatch_request(method, method_len, path, path_len, &resp, &resp_len, &handler_idx);

$dynamic_dispatch

                /* Check for Connection: close */
                int keep_alive = 1;
                if (len > 20) {
                    const char* conn = strstr(recv_buf + 16, "onnection:");
                    if (conn && (conn[-1] == 'C' || conn[-1] == 'c')) {
                        if (strstr(conn, "close") || strstr(conn, "Close")) {
                            keep_alive = 0;
                        }
                    }
                }

                if (!keep_alive) {
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
                    close(fd);
                }
            }
        }
    }

    close(epoll_fd);
    XSRETURN(0);
}
C
}

sub _gen_dynamic_handler_caller {
    my ($self) = @_;

    # Generate C code to call Perl handlers for dynamic routes
    # This uses call_sv to invoke the handler with request data
    return <<'C';
/* Call a dynamic Perl handler and format HTTP response */
static void call_dynamic_handler(pTHX_ int handler_idx,
                                  const char* method, int method_len,
                                  const char* path, int path_len,
                                  const char* body, int body_len,
                                  char** resp_out, int* resp_len_out) {
    dSP;
    int count;
    SV* result;
    STRLEN len;
    const char* body_str;

    if (!g_handler_array) {
        *resp_out = (char*)RESP_404;
        *resp_len_out = RESP_404_LEN;
        return;
    }

    AV* handlers = (AV*)SvRV(g_handler_array);
    SV** handler_sv = av_fetch(handlers, handler_idx, 0);
    if (!handler_sv || !SvROK(*handler_sv)) {
        *resp_out = (char*)RESP_404;
        *resp_len_out = RESP_404_LEN;
        return;
    }

    /* Build request hash for the handler */
    HV* req = newHV();
    hv_store(req, "method", 6, newSVpvn(method, method_len), 0);
    hv_store(req, "path", 4, newSVpvn(path, path_len), 0);
    hv_store(req, "body", 4, newSVpvn(body, body_len), 0);

    /* Extract path parameters if present */
    /* For /users/123, extract 123 as 'id' parameter */
    const char* last_slash = path + path_len;
    while (last_slash > path && *(last_slash - 1) != '/') last_slash--;
    if (last_slash > path) {
        int param_len = (path + path_len) - last_slash;
        if (param_len > 0) {
            hv_store(req, "id", 2, newSVpvn(last_slash, param_len), 0);
        }
    }

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    XPUSHs(sv_2mortal(newRV_noinc((SV*)req)));
    PUTBACK;

    count = call_sv(*handler_sv, G_SCALAR | G_EVAL);

    SPAGAIN;

    if (SvTRUE(ERRSV)) {
        /* Handler threw an error */
        static char error_resp[512];
        int err_len = snprintf(error_resp, sizeof(error_resp),
            "HTTP/1.1 500 Internal Server Error\r\n"
            "Content-Type: text/plain\r\n"
            "Content-Length: 21\r\n"
            "Connection: close\r\n\r\n"
            "Internal Server Error");
        *resp_out = error_resp;
        *resp_len_out = err_len;
        POPs; /* pop the undef */
    } else if (count == 1) {
        result = POPs;
        if (SvOK(result)) {
            body_str = SvPV(result, len);

            /* Determine content type */
            const char* ct = "text/plain";
            if (len > 0 && (body_str[0] == '{' || body_str[0] == '[')) {
                ct = "application/json";
            }

            /* Build response - use static buffer */
            static __thread char resp_buf[65536];
            int hdr_len = snprintf(resp_buf, 512,
                "HTTP/1.1 200 OK\r\n"
                "Content-Type: %s\r\n"
                "Content-Length: %zu\r\n"
                "Connection: keep-alive\r\n\r\n",
                ct, len);

            if (hdr_len + len < sizeof(resp_buf)) {
                memcpy(resp_buf + hdr_len, body_str, len);
                *resp_out = resp_buf;
                *resp_len_out = hdr_len + (int)len;
            } else {
                *resp_out = (char*)RESP_404;
                *resp_len_out = RESP_404_LEN;
            }
        } else {
            *resp_out = (char*)RESP_404;
            *resp_len_out = RESP_404_LEN;
        }
    } else {
        *resp_out = (char*)RESP_404;
        *resp_len_out = RESP_404_LEN;
    }

    PUTBACK;
    FREETMPS;
    LEAVE;
}
C
}

sub _escape_c_string {
    my ($str) = @_;
    $str =~ s/\\/\\\\/g;
    $str =~ s/"/\\"/g;
    $str =~ s/\n/\\n/g;
    $str =~ s/\r/\\r/g;
    $str =~ s/\t/\\t/g;
    return $str;
}

sub dispatch {
    my ($self, $req) = @_;
    die "Must call compile() first" unless $self->{compiled};
    return $self->{dispatch_fn}->($req);
}

sub run {
    my ($self, %opts) = @_;

    die "Must call compile() before run()" unless $self->{compiled};

    my $host    = $opts{host}    // '0.0.0.0';
    my $port    = $opts{port}    // 8080;
    my $workers = $opts{workers} // 1;

    # Create listening socket
    my $listen_fd = Hypersonic::Socket::create_listen_socket($port);
    die "Failed to create listen socket on port $port" if $listen_fd < 0;

    print "Hypersonic listening on $host:$port (pure C event loop)\n";

    # Fork workers if requested
    if ($workers > 1) {
        for my $i (1 .. $workers - 1) {
            my $pid = fork();
            if (!defined $pid) {
                die "Fork failed: $!";
            }
            if ($pid == 0) {
                # Child - run pure C event loop (never returns)
                # Pass self for dynamic handler access
                $self->{run_loop_fn}->($listen_fd, $self);
                exit(0);
            }
        }
    }

    # Parent (or single worker) runs pure C event loop (never returns)
    # Pass self for dynamic handler access
    $self->{run_loop_fn}->($listen_fd, $self);
}


1;

__END__

=head1 NAME

Hypersonic - Blazing fast HTTP server with JIT-compiled C event loop

=head1 SYNOPSIS

    use Hypersonic;

    my $server = Hypersonic->new();

    # Handlers return STRINGS - they run ONCE at compile time
    $server->get('/api/hello' => sub {
        '{"message":"Hello, World!"}'
    });

    $server->get('/health' => sub {
        'OK'
    });

    # Compile routes - generates C code and compiles via XS::JIT
    $server->compile();

    # Run the server
    $server->run(
        port    => 8080,
        workers => 4,
    );

=head1 DESCRIPTION

Hypersonic is a benchmark-focused micro HTTP server that uses XS::JIT to
generate and compile C code at runtime. The entire event loop runs in C
with no Perl in the hot path.

B<What it does:>

=over 4

=item 1. Route handlers run ONCE at C<compile()> time

=item 2. Response strings (including HTTP headers) are baked into C as static constants

=item 3. A pure C event loop (kqueue/epoll) is generated and compiled

=item 4. At runtime, the C code handles recv/dispatch/send with zero Perl calls

=back

B<What it is NOT:>

This does NOT use Perl custom ops (like Meow's accessors). It generates
XS functions that are called once to enter the C event loop. The speed
comes from the pure C event loop, not from op-tree manipulation.

B<Performance:> ~290,000 requests/second on a single core (macOS/kqueue).

=head1 METHODS

=head2 new

    my $server = Hypersonic->new(%options);

Create a new Hypersonic server instance.

Options:

=over 4

=item cache_dir

Directory for caching compiled XS modules. Default: C<_hypersonic_cache>

=back

=head2 get, post, put, del, patch, head, options

    # Static route - handler runs ONCE at compile time
    $server->get('/path' => sub { 'response string' });

    # Dynamic route - handler runs PER REQUEST
    $server->get('/users/:id' => sub {
        my ($req) = @_;
        my $id = $req->{id};
        return qq({"id":"$id"});
    });

    # Explicit dynamic route
    $server->post('/api/data' => sub {
        my ($req) = @_;
        my $body = $req->{body};
        return qq({"received":"$body"});
    }, { dynamic => 1 });

Register a route handler. The handler must return a string.

Routes with path parameters (C<:param>) are automatically dynamic.
Use C<< { dynamic => 1 } >> to force a route to be dynamic.

Dynamic handlers receive a hashref with: C<method>, C<path>, C<body>, C<id> (if path param).

=head2 compile

    $server->compile();

Compile all registered routes into JIT'd native code. This:

1. Executes each handler once to get the response string
2. Generates C code with the strings as static constants
3. Compiles the C code via XS::JIT

=head2 dispatch

    my $response = $server->dispatch(['GET', '/api/hello', '', 1, 0]);

Dispatch a request and return the response. Request is an arrayref:
C<[method, path, body, keep_alive, fd]>

=head2 run

    $server->run(port => 8080, workers => 4);

Start the HTTP server. (Event loop implementation coming in Phase 2)


=head1 benchmark

	======================================================================
	Benchmark: Route matching for GET /api/hello
	======================================================================

	Benchmark: running Dancer2, HTTP_Router, Hypersonic, Mojolicious, Plack for at least 3 CPU seconds...
	   Dancer2:  3 wallclock secs ( 3.15 usr +  0.01 sys =  3.16 CPU) @ 17713.29/s (n=55974)
	HTTP_Router:  3 wallclock secs ( 3.15 usr +  0.01 sys =  3.16 CPU) @ 107178.48/s (n=338684)
	Hypersonic:  2 wallclock secs ( 3.07 usr +  0.00 sys =  3.07 CPU) @ 9336325.08/s (n=28662518)
	Mojolicious:  4 wallclock secs ( 3.12 usr +  0.02 sys =  3.14 CPU) @ 196110.19/s (n=615786)
	     Plack:  4 wallclock secs ( 3.06 usr +  0.02 sys =  3.08 CPU) @ 3937159.09/s (n=12126450)

	Comparison (higher is better):
			 Rate     Dancer2 HTTP_Router Mojolicious       Plack Hypersonic
	Dancer2       17713/s          --        -83%        -91%       -100%      -100%
	HTTP_Router  107178/s        505%          --        -45%        -97%       -99%
	Mojolicious  196110/s       1007%         83%          --        -95%       -98%
	Plack       3937159/s      22127%       3573%       1908%          --       -58%
	Hypersonic  9336325/s      52608%       8611%       4661%        137%         --

=head1 AUTHOR

LNATION <email@lnation.org>

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
