package PAGI::FastAPI::Context;

use v5.38;
use experimental 'class';
use version;

our $VERSION   = qv('v1.0.0');
our $AUTHORITY = 'cpan:MANWAR';

class PAGI::FastAPI::Context {
    use Future::IO;

    field $scope        :param = {};
    field $query_params :param = {};
    field $path_params  :param = {};
    field $body         :param = undef;
    field $status       :param = 200;
    field $res_headers  :param = [];
    field $stash        :param = {};
    field $pagi_context :param = undef;

    method scope { $scope }

    method sleep ($seconds) {
        return Future::IO->sleep($seconds);
    }

    method status ($val = undef) {
        if (defined $val) {
            $status = $val;
        }
        return $status;
    }

    method pagi_context { $pagi_context }

    method csrf_token () {
        # 1. Direct scope environment
        if (defined $scope
            && ref $scope eq 'HASH'
            && $scope->{'pagi.csrf_token'}) {
            return $scope->{'pagi.csrf_token'};
        }

        # 2. PAGI context environment
        if (defined $pagi_context
            && $pagi_context->can('env')) {
            my $env = $pagi_context->env;
            return $env->{'pagi.csrf_token'}
                if $env && $env->{'pagi.csrf_token'};
        }

        # 3. Session storage fallback
        if (defined $scope
            && $scope->{'pagi.session'}
            && ref $scope->{'pagi.session'} eq 'HASH') {
            return $scope->{'pagi.session'}{'csrf_token'};
        }

        return undef;
    }

    method csrf_verify ($token) {
        die "PAGI context is not set" unless defined $pagi_context;
        return $pagi_context->csrf_verify($token);
    }

    method res_headers { $res_headers }

    method set_header ($key, $val) {
        push @$res_headers, [$key, $val];
    }

    method header ($name) {
        my $headers = $scope->{headers} // [];
        my $lc_name = lc($name);
        for my $h (@$headers) {
            return $h->[1] if lc($h->[0]) eq $lc_name;
        }
        return undef;
    }

    method html ($content, %opts) {
        use PAGI::FastAPI::Response::HTML;

        return PAGI::FastAPI::Response::HTML->new(
            body    => $content,
            headers => $opts{headers} // [],
            status  => $opts{status}  // 200,
        );
    }

    method sse ($code) {
        use PAGI::FastAPI::Response::SSE;

        return PAGI::FastAPI::Response::SSE->new(
            generator => $code,
        );
    }

    method path_params  { $path_params }
    method query_params { $query_params }

    method path_param ($name) {
        return $path_params->{$name};
    }

    method query_param ($name) {
        return $query_params->{$name};
    }

    method body ($key = undef) {
        if (defined $key) {
            return (ref $body eq 'HASH') ? $body->{$key} : undef;
        }
        return $body;
    }

    method param ($key) {
        return $self->path_param($key)
            // $self->query_param($key)
            // $self->body($key);
    }

    method stash { $stash }
}

=encoding utf-8

=head1 NAME

PAGI::FastAPI::Context - Request and Response Lifecycle Context for PAGI::FastAPI

=head1 VERSION

Version v1.0.0

=head1 SYNOPSIS

    # Inspecting Request Parameters
    my $user_id = $c->path_param('id');
    my $limit   = $c->query_param('limit');
    my $name    = $c->body('name');

    # Generic Parameter Fallback (Path -> Query -> Body)
    my $token   = $c->param('token');

    # Headers & Scope
    my $ua      = $c->header('User-Agent');
    my $scope   = $c->scope;

    # Stash Storage
    $c->stash->{user} = { id => 42, role => 'admin' };

    # Modifying Response State
    $c->status(201);
    $c->set_header('X-Custom-Header' => 'value');

=head1 DESCRIPTION

C<PAGI::FastAPI::Context> encapsulates the request environment, parsed parameters,
payload data, and response state for an individual HTTP exchange processed
by L<PAGI::FastAPI>.

An instance of this context is passed as the primary argument to route handlers,
middleware functions, and dependency blocks.

=head1 METHODS

=head2 C<new(%args)>

Constructor called internally by L<PAGI::FastAPI>. Accepts named arguments:

=over 4

=item * C<scope> - PAGI environment HashRef.

=item * C<query_params> - HashRef of validated query parameters.

=item * C<path_params> - HashRef of route path variables.

=item * C<body> - Decoded payload body (HashRef, ArrayRef, or Scalar).

=item * C<status> - Initial HTTP response code (default: C<200>).

=item * C<res_headers> - Initial response headers ArrayRef (default: C<[]>).

=item * C<stash> - Context-bound storage HashRef (default: C<{}>).

=back

=head2 C<scope()>

Returns the raw PAGI scope HashRef for the current request.

=head2 C<sleep($seconds)>

    await $c->sleep(1);

Asynchronously pauses execution for the given number of seconds without blocking the event loop.

Uses L<Future::IO> under the hood to ensure non-blocking sleep operations.

=over 4

=item * C<$seconds>

Number of seconds to sleep (fractional seconds like C<0.5> are supported).

=back

Returns a L<Future> that completes when the specified sleep duration has elapsed.

=head2 C<status([ $code ])>

Gets or sets the HTTP status code for the response.

    $c->status(403);
    my $code = $c->status; # 403

=head2 C<pagi_context>

    my $pagi_ctx = $c->pagi_context;

Returns the underlying low-level L<PAGI::Context> instance associated with
the current HTTP request. Useful for low-level protocol inspection, raw
environment access, or invoking protocol-specific extension methods.

=head2 C<csrf_token>

    my $token = $c->csrf_token;

Retrieves the active Anti-CSRF token for the current request.

This method transparently attempts to resolve the token from three potential
locations in order of precedence:

=over 4

=item 1. B<Direct Scope Environment:> C<< $scope->{'pagi.csrf_token'} >> (set directly by L<PAGI::Middleware::CSRF>).

=item 2. B<PAGI Context Environment:> C<< $pagi_context->env->{'pagi.csrf_token'} >>.

=item 3. B<Session Storage Fallback:> C<< $scope->{'pagi.session'}{'csrf_token'} >> (set by session management middleware).

=back

Returns the scalar token string if found, or C<undef> if no token is
available or if CSRF/Session middleware is not active for the request.

B<Example Usage (Embedding in HTML forms):>

    $app->get('/form', handler => async sub ($c) {
        my $token = $c->csrf_token // '';
        return $c->html(qq{
            <form method="POST" action="/submit">
                <input type="hidden" name="csrf_token" value="$token">
                <button type="submit">Submit</button>
            </form>
        });
    });

=head2 C<csrf_verify($token)>

    my $is_valid = $c->csrf_verify($submitted_token);

Explicitly validates the given C<$token> against the current request's CSRF
state by delegating to the underlying low-level L<PAGI::Context> instance.

Accepts a scalar token string C<$token>. Returns a true value if the token
signature and expiration are valid; returns false otherwise.

Dies with C<"PAGI context is not set"> if invoked when no low-level
L<PAGI::Context> instance is associated with C<$c>.

B<Example Usage (Manual Verification):>

    $app->post('/api/action', handler => async sub ($c) {
        my $token = $c->body('csrf_token');

        unless ($c->csrf_verify($token)) {
            $c->status(403);
            return { error => 'Invalid or missing CSRF token' };
        }

        return { status => 'success' };
    });

=head2 C<res_headers()>

Returns the current list of outgoing response header pairs as an ArrayRef of
tuple pairs C<[ [$name, $val], ... ]>.

=head2 C<set_header($key, $val)>

Appends an outgoing HTTP header pair to the response headers list.

    $c->set_header('X-Frame-Options' => 'DENY');

=head2 C<header($name)>

Case-insensitively searches incoming request headers (from C<< $c->scope->{headers} >>)
and returns its scalar value, or C<undef> if missing.

    my $auth = $c->header('Authorization');

=head2 C<html($content, %options)>

    $app->get('/about', handler => async sub ($c) {
        return $c->html('<h1>About Us</h1>');
    });

Returns an HTTP response with the C<Content-Type> header automatically set to C<text/html; charset=utf-8>.

Accepts the HTML content string as the first parameter, followed by optional named parameters:

=over 4

=item * C<status> (Optional)

Integer HTTP status code. Defaults to C<200>.

=item * C<headers> (Optional)

ArrayRef of additional header key-value pairs.

=back

Returns a C<PAGI::FastAPI::Response> object.

=head2 C<sse($code)>

    $app->get('/api/v1/metrics', handler => async sub ($c) {
        return $c->sse(async sub ($stream) {
            while (1) {
                await $stream->send_json({ cpu => 42 });
                await $c->sleep(1);
            }
        });
    });

Creates and returns a Server-Sent Events (SSE) response object.

Accepts an async generator coderef that receives an SSE stream handler as
its first argument.

=over 4

=item * C<$code>

An C<async sub> coderef that defines the event streaming loop. The callback
receives an instance of L<PAGI::SSE>, offering methods such as
C<send_event()>, C<send_json()>, C<send()>, C<keepalive()>, and C<close()>.

=back

Returns an instance of L<PAGI::FastAPI::Response::SSE>.

=head2 C<path_params()>

Returns the HashRef containing all parsed path parameters.

=head2 C<path_param($key)>

Returns a specific parsed path parameter by name, or C<undef> if absent.

=head2 C<query_params()>

Returns the HashRef containing all parsed query parameters.

=head2 C<query_param($key)>

Returns a specific parsed query parameter by name, or C<undef> if absent.

=head2 C<body([ $key ])>

If called without parameters, returns the full raw/decoded request body.

If called with a C<$key> parameter and the body is a HashRef, returns the
value for that key, or C<undef> if missing or if the body is not a HashRef.

    my $full_body = $c->body;
    my $user_name = $c->body('username');

=head2 C<param($key)>

Convenience parameter accessor that checks parameter stores in priority order:

1. Path parameters (C<path_param>)
2. Query parameters (C<query_param>)
3. JSON/Body fields (C<body($key)>)

Returns the first matching non-undef value, or C<undef> if the key is not
present in any store.

=head2 C<stash()>

Returns a HashRef tied to the lifecycle of this context. Useful for sharing
data between middleware, dependency injection blocks, and final route
handlers.

    $c->stash->{db_session} = $db;

=head1 AUTHOR

Mohammad Sajid Anwar, C<< <mohammad.anwar at yahoo.com> >>

=head1 REPOSITORY

L<https://github.com/manwar/PAGI-FastAPI>

=head1 BUGS

Please report any bugs or feature requests through the web interface at L<https://github.com/manwar/PAGI-FastAPI/issues>.
I will be notified and then you'll automatically be notified of progress on your
bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc PAGI::FastAPI::Context

You can also look for information at:

=over 4

=item * BUG Report

L<https://github.com/manwar/PAGI-FastAPI/issues>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/PAGI-FastAPI>

=item * Search MetaCPAN

L<https://metacpan.org/dist/PAGI-FastAPI/>

=back

=head1 LICENSE AND COPYRIGHT

Copyright (C) 2026 Mohammad Sajid Anwar.

This program is free software; you can redistribute it and/or modify it under
the terms of the Artistic License (2.0).

=cut

1; # End of PAGI::FastAPI::Context
