package Thunderhorse::Router::Location;
$Thunderhorse::Router::Location::VERSION = '0.001';
use v5.40;
use Mooish::Base -standard;

use Gears::X::Thunderhorse;

use Future::AsyncAwait;
use HTTP::Status qw(status_message);

extends 'Gears::Router::Location::SigilMatch';

has option 'action' => (
	isa => Str,
);

has field '_action_re' => (
	isa => RegexpRef,
	lazy => 1,
);

has param 'name' => (
	isa => Str,
	lazy => 1,
);

has param 'to' => (
	isa => Str | CodeRef,
	required => 0,
);

has param 'order' => (
	isa => Int,
	default => 0,
);

has param 'pagi' => (
	isa => Bool,
	default => false,
);

has param 'pagi_middleware' => (
	isa => CodeRef,
	required => 0,
);

has param 'controller' => (
	isa => InstanceOf ['Thunderhorse::Controller'],
);

has param 'pagi_app' => (
	isa => CodeRef,
	lazy => 1,
);

sub BUILD ($self, $)
{
	Gears::X::Thunderhorse->raise('controller has no action ' . $self->to)
		if defined $self->to && !$self->get_destination;

	# register the route in the router
	$self->router->_register_location($self->name, $self);
}

sub _build_action_re ($self)
{
	my ($scope, $method) = split /\./, $self->action;
	$scope = $scope eq '*' ? qr{[^.]+} : quotemeta $scope;
	$method = ($method // '*') eq '*' ? qr{(\.[^.]+)?} : quotemeta ".$method";

	return qr{^$scope$method$};
}

sub _build_name ($self)
{
	my $action = $self->action // 'any';
	my $pattern = $self->pattern;
	my $id = $self->router->_get_next_route_id;

	# autogenerated location name should be readable, but unique
	return "${id}_${action}_${pattern}";
}

sub _build_pagi_app ($self)
{
	weaken $self;
	my $dest = $self->get_destination;
	my $controller = $self->controller;
	my $pagi;

	if ($self->pagi) {
		# TODO: adjust PAGI (like Kelp did to PSGI)
		$pagi = async sub ($scope, @args) {
			Gears::X::Thunderhorse->raise('bad PAGI execution chain, not a Thunderhorse app')
				unless exists $scope->{thunderhorse};

			my $result = await $dest->($scope, @args);
			$scope->{thunderhorse}->consume;

			return $result;
		}
	}
	else {
		$pagi = async sub ($scope, $receive, $send) {
			Gears::X::Thunderhorse->raise('bad PAGI execution chain, not a Thunderhorse app')
				unless exists $scope->{thunderhorse};

			my $ctx = $scope->{thunderhorse};
			$ctx->set_pagi([$scope, $receive, $send]);

			my $match = $ctx->match;
			my $bridge = ref $match eq 'ARRAY';

			if (defined $dest) {
				try {
					my $facade = $controller->make_facade($ctx);
					my $result = $dest->($controller, $facade, ($bridge ? $match->[0] : $match)->matched->@*);
					$result = await $result
						if $result isa 'Future';

					if (!$ctx->is_consumed) {
						if (defined $result) {
							await $ctx->res->status_try(200)->content_type_try('text/html')->send($result);
						}
						else {
							weaken $facade;
							Gears::X::Thunderhorse->raise("context hasn't been given up - forgot await?")
								if defined $facade;
						}
					}
				}
				catch ($ex) {
					await $controller->on_error($ctx, $ex);
				}
			}

			# if this is a bridge and bridge did not render, it means we are
			# free to go deeper. Avoid first match, as it was handled already
			# above
			if ($bridge && !$ctx->is_consumed) {
				await $controller->app->pagi_loop($ctx, $match->@[1 .. $match->$#*]);
			}
		};
	}

	if (my $mw = $self->pagi_middleware) {
		$pagi = $mw->($pagi);
	}

	return $pagi;
}

sub get_destination ($self)
{
	my $to = $self->to;
	return undef unless defined $to;

	return $to if ref $to eq 'CODE';
	return $self->controller->can($self->to);
}

sub compare ($self, $path, $action)
{
	return undef if $self->has_action && $action !~ $self->_action_re;
	return $self->SUPER::compare($path);
}

