#!/usr/bin/env perl

# Handle compile-time options before loading any modules
# Future::XS must be loaded before Future, so check early
BEGIN {
    my @to_splice;
    for my $i (0 .. $#ARGV) {
        if ($ARGV[$i] eq '--future-xs') {
            $ENV{PAGI_FUTURE_XS} = 1;
            push @to_splice, $i;
        }
        elsif ($ARGV[$i] eq '--http2') {
            $ENV{_PAGI_SERVER_HTTP2} = 1;
            push @to_splice, $i;
        }
    }
    # Remove processed flags in reverse order to preserve indices
    for my $i (reverse @to_splice) {
        splice @ARGV, $i, 1;
    }
}

use strict;
use warnings;

use PAGI::Runner;

PAGI::Runner->run(@ARGV);

__END__

=encoding UTF-8

=head1 NAME

pagi-server - PAGI application server

=head1 SYNOPSIS

    pagi-server [options] [app] [key=value ...]

    # Serve current directory (default)
    pagi-server

    # Serve a specific directory
    pagi-server PAGI::App::Directory root=/var/www

    # Run a PAGI app file
    pagi-server ./app.pl

    # With environment modes
    pagi-server -E development app.pl  # Lint middleware enabled
    pagi-server -E production app.pl   # No auto-middleware

    # With options
    pagi-server -p 8080 PAGI::App::Directory root=/var/www
    pagi-server --workers 4 ./myapp.pl

=head1 DESCRIPTION

pagi-server is the command-line interface for running PAGI applications.
It uses L<PAGI::Runner> for application loading and server orchestration,
and L<PAGI::Server> as the default server backend.

=head1 ENVIRONMENT MODES

pagi-server supports environment modes similar to Plack's C<-E> flag:

=over 4

=item B<development>

Auto-enables L<PAGI::Middleware::Lint> with strict mode. This catches
specification violations early and provides helpful error messages.

This is the B<default when running interactively> (TTY detected).

=item B<production>

No middleware is auto-enabled, and access logging is disabled for maximum
performance. Use C<--access-log FILE> to enable logging in production.

This is the B<default when running non-interactively> (no TTY, e.g.,
systemd, docker, cron).

=item B<none>

Explicit opt-out of all auto-middleware, regardless of TTY detection.

=back

Mode is determined by (in order of precedence):

    1. -E / --env command line flag
    2. PAGI_ENV environment variable
    3. Auto-detection: TTY = development, no TTY = production

After determining the mode, C<pagi-server> sets C<PAGI_ENV> to the resolved
value. Your app can check C<$ENV{PAGI_ENV}> to know what mode it's running
in, similar to Plack's C<PLACK_ENV>.

=head1 OPTIONS

=head2 Common Options

These are handled by L<PAGI::Runner> and work with any server backend.

=over 4

=item B<-I>, B<--lib> PATH

Add PATH to C<@INC> before loading the app. Can be specified multiple
times, similar to C<perl -I>.

    pagi-server -I ./lib -I ./vendor/lib ./app.pl

=item B<-a>, B<--app> FILE

Path to .pl/.psgi file returning PAGI app coderef. Legacy option for
backward compatibility; you can also just pass the file as an argument.

=item B<-e> CODE

Inline app code, like C<perl -e>. Must return a PAGI app coderef.

    # Using a PAGI::App module
    pagi-server -MPAGI::App::File -e 'PAGI::App::File->new(root => ".")->to_app'

=item B<-M> MODULE

Load MODULE before evaluating C<-e> code, like C<perl -M>. Can be
specified multiple times. Supports C<Module=import,args> syntax.

    pagi-server -MPAGI::App::File -e 'PAGI::App::File->new(root => ".")->to_app'

=item B<-o>, B<--host> HOST

Bind address (default: 127.0.0.1, localhost only)

The default only accepts connections from localhost, which is B<secure by
default> for development. For remote access:

    pagi-server --host 0.0.0.0 ./app.pl    # All IPv4 interfaces

=item B<-p>, B<--port> PORT

Bind port (default: 5000)

=item B<-s>, B<--server> CLASS

Server class to use (default: PAGI::Server). Reserved for future use
with pluggable server backends.

=item B<-E>, B<--env> MODE

Environment mode: development, production, or none.

=item B<-l>, B<--loop> BACKEND

Event loop backend (Poll, EV, Epoll, UV). Default is auto-detect.

=item B<--access-log> FILE

Path to access log file (default: STDERR)

=item B<--no-access-log>

Disable access logging entirely. Improves throughput by 5-15%.

=item B<-D>, B<--daemonize>

Fork to background and run as a daemon.

=item B<--pid> FILE

Write the process ID to FILE.

=item B<--user> USER

After binding, drop privileges to run as USER. Requires root.

=item B<--group> GROUP

After binding, drop privileges to run as GROUP.

=item B<-q>, B<--quiet>

Suppress startup banner and development mode message.

=item B<--no-default-middleware>

Disable auto-middleware even in development mode.

=item B<-v>, B<--version>

Show version info.

=item B<--help>

Show help.

=back

=head2 PAGI::Server Options

These options are passed through to L<PAGI::Server>.

=over 4

=item B<-w>, B<--workers> NUM

Number of worker processes (default: 1, single-process mode)

=item B<--reuseport>

Enable SO_REUSEPORT mode for multi-worker servers.

=item B<--ssl-cert> FILE, B<--ssl-key> FILE

Enable HTTPS with the specified certificate and key files.

=item B<--max-requests> NUM

Maximum requests per worker before restart (default: 0, unlimited)

=item B<--max-connections> NUM

Maximum concurrent connections per worker (default: auto-detect)

=item B<--max-body-size> NUM

Maximum request body size in bytes (default: 10MB)

=item B<--timeout> NUM

Connection idle timeout in seconds (default: 60)

=item B<--shutdown-timeout> NUM

Graceful shutdown timeout in seconds (default: 30)

=item B<--log-level> LEVEL

Log verbosity: debug, info, warn, error (default: info)

=item B<-b>, B<--listener-backlog> NUM

Listen queue size (default: 2048)

=item B<--max-receive-queue> NUM

Maximum WebSocket receive queue size (default: 1000)

=item B<--max-ws-frame-size> NUM

Maximum WebSocket frame size in bytes (default: 65536)

=item B<--request-timeout> NUM

Request stall timeout in seconds (default: 0, disabled)

=item B<--ws-idle-timeout> NUM

WebSocket idle timeout in seconds (default: 0, disabled)

=item B<--sse-idle-timeout> NUM

SSE idle timeout in seconds (default: 0, disabled)

=item B<--heartbeat-timeout> NUM

Worker heartbeat timeout in seconds (default: 50, 0 = disabled).
Only active in multi-worker mode. Workers send periodic heartbeats to
the parent via a Unix pipe. If a worker's event loop is blocked for
longer than this timeout (blocking syscall, deadlock, CPU-bound work),
the parent kills it with SIGKILL and respawns a replacement.

This should be larger than the maximum time you expect any single
operation to block the event loop. Async operations using C<await> do
not block the event loop and will never trigger this timeout.

=item B<--sync-file-threshold> NUM

Sync file read threshold in bytes (default: 65536)

=item B<--access-log-format> FORMAT

Access log format string or preset name (default: C<clf>).

Preset names: C<clf>, C<combined>, C<common>, C<tiny>.

Custom format strings use Apache-style atoms:

    --access-log-format combined
    --access-log-format '%h %s %Dms'

See L<PAGI::Server/ACCESS LOG FORMAT> for the full atoms table.

=item B<--future-xs>

Enable L<Future::XS> for improved Future performance.
Must be installed separately: C<cpanm Future::XS>

Can also be enabled via environment variable: C<PAGI_FUTURE_XS=1>

=item B<--http2>

Enable HTTP/2 support. Requires L<Net::HTTP2::nghttp2>.

When combined with C<--ssl-cert>/C<--ssl-key>, uses ALPN negotiation
(h2/http/1.1). Without TLS, enables h2c (cleartext HTTP/2) mode.

See L<PAGI::Server> for HTTP/2 protocol tuning options
(C<h2_max_concurrent_streams>, C<h2_initial_window_size>, etc.)
which can be passed via the constructor API.

=back

=head1 APP SPECIFICATION

=head2 Module Name

If the argument contains C<::>, it's treated as a Perl module name:

    pagi-server PAGI::App::Directory root=/var/www

The module must have C<new()> and C<to_app()> methods.

=head2 File Path

If the argument contains C</> or ends with C<.pl>/C<.psgi>:

    pagi-server ./app.pl

The file must return a PAGI app coderef.

=head2 Default

If no app is specified, serves the current directory:

    pagi-server
    # equivalent to: pagi-server PAGI::App::Directory root=.

=head1 SIGNAL HANDLING

=over 4

=item B<TERM>, B<INT>

Graceful shutdown.

=item B<HUP>

Graceful restart (multi-worker mode only).

=item B<TTIN>, B<TTOU>

Increase/decrease worker count (multi-worker mode only).

=back

=head1 EXAMPLES

    # Development (auto-detected from TTY)
    pagi-server ./app.pl

    # Explicit production mode
    pagi-server -E production ./app.pl
    PAGI_ENV=production pagi-server ./app.pl

    # Multi-worker production
    pagi-server --host 0.0.0.0 -p 80 --workers 4 -E production ./app.pl

    # With HTTPS
    pagi-server --ssl-cert cert.pem --ssl-key key.pem ./app.pl

    # Maximum performance
    pagi-server --no-access-log --workers 8 -E production ./app.pl

    # Using EV event loop
    pagi-server --loop EV ./app.pl

=head1 ENVIRONMENT

=over 4

=item B<PAGI_ENV>

Default environment mode if C<-E> is not specified and no TTY detected.

=item B<IO_ASYNC_LOOP>

Preferred IO::Async loop backend.

=back

=head1 FUTURE::IO INTEGRATION

C<pagi-server> automatically configures L<Future::IO> to use the IO::Async
backend if L<Future::IO::Impl::IOAsync> is installed. This enables seamless
use of Future::IO-based libraries in your PAGI applications:

    # These just work under pagi-server:
    use Async::Redis;
    use Some::FutureIO::Database;

    # PAGI::SSE->every() also works:
    $sse->every(1, sub { $sse->send(data => time) });

In non-production mode, pagi-server will report when Future::IO is configured:

    Future::IO configured for IO::Async

If you're using L<PAGI::Server> programmatically (not via pagi-server), you
must configure Future::IO yourself. See L<PAGI::Server/"LOOP INTEROPERABILITY">
for details.

=head1 SEE ALSO

L<PAGI::Runner>, L<PAGI::Server>, L<PAGI::Middleware::Lint>, L<Future::IO>

=head1 AUTHOR

John Napiorkowski E<lt>jjnapiork@cpan.orgE<gt>

=head1 LICENSE

This software is licensed under the same terms as Perl itself.

=cut
