The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Courriel::Role::HeaderWithAttributes;

use strict;
use warnings;
use namespace::autoclean;

our $VERSION = '0.37';

use Courriel::HeaderAttribute;
use Courriel::Helpers qw( parse_header_with_attributes );
use Courriel::Types qw( HashRef NonEmptyStr );
use MooseX::Params::Validate qw( pos_validated_list validated_list );
use Scalar::Util qw( blessed reftype );

use MooseX::Role::Parameterized;

parameter main_value_key => (
    isa      => NonEmptyStr,
    required => 1,
);

parameter main_value_method => (
    isa => NonEmptyStr,
);

has _attributes => (
    traits   => ['Hash'],
    is       => 'ro',
    isa      => HashRef ['Courriel::HeaderAttribute'],
    init_arg => 'attributes',
    default  => sub { {} },
    handles  => {
        attributes      => 'elements',
        _attribute      => 'get',
        _set_attribute  => 'set',
        _has_attributes => 'count',
    },
);

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;

    my $p = $class->$orig(@_);

    return $p
        unless $p->{attributes} && reftype( $p->{attributes} ) eq 'HASH';

    for my $name ( keys %{ $p->{attributes} } ) {
        my $lc_name = lc $name;
        $p->{attributes}{$lc_name} = delete $p->{attributes}{$name};

        next if blessed( $p->{attributes}{$lc_name} );

        $p->{attributes}{$lc_name} = Courriel::HeaderAttribute->new(
            name  => $name,
            value => $p->{attributes}{$name},
        );
    }

    return $p;
};

sub attribute {
    my $self = shift;
    my $key  = shift;

    return unless defined $key;

    return $self->_attribute( lc $key );
}

{
    my @spec = ( { isa => NonEmptyStr } );

    sub attribute_value {
        my $self = shift;
        my ($name) = pos_validated_list( \@_, @spec );

        my $attr = $self->attribute($name);

        return $attr ? $attr->value() : undef;
    }
}

sub _attributes_as_string {
    my $self = shift;

    my $attr = $self->_attributes();

    return join '; ', map { $attr->{$_}->as_string() } sort keys %{$attr};
}

{
    my @spec = (
        name  => { isa => NonEmptyStr, optional => 1 },
        value => { isa => NonEmptyStr },
    );

    role {
        my $p = shift;

        my $main_value_key = $p->main_value_key();

        method new_from_value => sub {
            my $class = shift;
            my ( $name, $value ) = validated_list( \@_, @spec );

            my ( $main_value, $attributes )
                = parse_header_with_attributes($value);

            my %p = (
                value           => $value,
                $main_value_key => $main_value,
                attributes      => $attributes,
            );

            $p{name} = $name if defined $name;

            return $class->new(%p);
        };

        my $main_value_meth = $p->main_value_method() || $p->main_value_key();

        method as_header_value => sub {
            my $self = shift;

            my $string = $self->$main_value_meth();

            if ( $self->_has_attributes() ) {
                $string .= '; ';
                $string .= $self->_attributes_as_string();
            }

            return $string;
        };
    }
}

1;