package Catalyst::View::Errors::JSON;

use Moose;
use JSON::MaybeXS;
use Catalyst::Utils::ContentNegotiation;
use Catalyst::Utils::ErrorMessages;

extends 'Catalyst::View';

has extra_encoder_args => (is=>'ro', required=>1, default => sub { +{} });

has encoder => (
  is => 'ro',
  init_arg => undef,
  required => 1,
  lazy => 1,
  default => sub { JSON::MaybeXS->new(utf8=>1, %{shift->extra_encoder_args}) },
);

has cn => (
  is => 'ro',
  init_arg => undef,
  required => 1, 
  default => sub { Catalyst::Utils::ContentNegotiation::content_negotiator },
);

has default_language => (is=>'ro', required=>1, default=>'en_US');

sub http_default {
  my ($self, $c, $code, %args) = @_;
  my $lang = $self->get_language($c);
  my $message_info = $self->finalize_message_info($c, $code, $lang, %args);

  my $json = $self->render_json($c, $message_info);
 
  $c->response->body($json);
  $c->response->content_type('application/json');
  $c->response->status($code);
}

sub get_language {
  my ($self, $c) = @_;
  if(my $lang = $c->request->header('Accept-Language')) {
    return $self->cn->choose_language([$self->available_languages($c)], $lang) || $self->default_language;
  }
  return $self->default_language;
}

sub available_languages {
  my ($self, $c) = @_;
  return my @lang_tags = Catalyst::Utils::ErrorMessages::available_languages;
}

sub finalize_message_info {
  my ($self, $c, $code, $lang, %args) = @_;
  my $message_info = $self->get_message_info($c, $lang, $code);
  return +{
    meta => {
      lang => $lang, 
      uri => delete($args{uri}), 
      %{$args{meta}||+{} },
    },
    errors => [
      {
        status => delete($args{code}),
        title => $message_info->{title},
        description => $message_info->{message},
      },
      @{$args{errors}||[] },
    ],
  };
}

sub get_message_info {
  my ($self, $c, $lang, $code) = @_;
  return my $message_info_hash = Catalyst::Utils::ErrorMessages::get_message_info($lang, $code);
}

sub render_json {
  my ($self, $c, $message_info) = @_;
  return $self->encoder->encode($message_info);
}

__PACKAGE__->meta->make_immutable;

=head1 NAME

Catalyst::View::Errors::JSON - Standard HTTP Errors Responses in JSON

=head1 SYNOPSIS

    package MyApp::View::JSON;

    use Moose;
    extends 'Catalyst::View::Errors::JSON';

    __PACKAGE__->meta->make_immutable;

=head1 DESCRIPTION

Used to generate a JSON error response in standard way.

=head1 METHODS

This view exposes the follow methods for public use or for a programmer to override
to change function.

=head2 available_languages

An array of the languages available for serving error responses.   By default we use
L<Catalyst::Utils::ErrorMessages> but if you have your own list of translations you can override
this.

=head2 get_message_info

Return error message info by code and language.  By default we use
L<Catalyst::Utils::ErrorMessages> but if you have your own list of translations you can override
this.

=head2 finalize_message_info

Finalizes the hash of data that is sent to the template handler to make the body of the error
response.  You can override if you want to change or add to this data.

By default you get an error message that looks like this:

    {
       "meta" : {
          "lang" : "en_US",
          "uri" : "http://localhost:5000/"
       },
       "errors" : [
          {
             "title" : "Resource not found",
             "description" : "The requested resource could not be found but may be available again in the future.",
             "status" : 404
          }
       ]
    }

When dispatching an error you can add to the C<meta> and C<errors> keys by passing information
via the arguments to C<$c->dispatch_error>.   For example:

    $c->dispatch_error(400 =>
      meta => { error_count = 2 },
      errors => [
        {
          field => 'name',
          message => 'too short',
        },
      errors => [
        {
          field => 'age',
          message => 'too young',
        },
      ],
    );

Will result in:

    {
       "meta" : {
          "lang" : "en_US",
          "uri" : "http://localhost:5000/"
       },
       "errors" : [
          {
             "title" : "Resource not found",
             "description" : "The requested resource could not be found but may be available again in the future.",
             "status" : 404
          },
          {
             "field" : "name",
             "message" : "too short",
          },
          {
             "field" : "age",
             "message" : "too old",
          }
       ]
    }

If you want a different error setup you'll need to override this method to do as you wish.    

=head1 CONFIGURATION

This View exposes the following configuration options

=head2 extra_encoder_args

Extra args used to initialize the L<JSON::MaybeXS> object.

=head2 default_language

When doing content negotiation if there's no language preferred by the client
use this language.   Default is C<en_US>.

=head1 SEE ALSO
 
L<CatalystX::Errors>.

=head1 AUTHOR
 
L<CatalystX::Errors>.
    
=head1 COPYRIGHT & LICENSE
 
L<CatalystX::Errors>.

=cut
