The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;

package HTML::FormFu::Role::Element::Layout;
$HTML::FormFu::Role::Element::Layout::VERSION = '2.06';
use Moose::Role;
use MooseX::Attribute::Chained;

use Carp qw( carp croak );
use List::MoreUtils qw( first_index );
use Scalar::Util qw( reftype );

use HTML::FormFu::Util qw( process_attrs );

has layout_errors_filename =>
    ( is => 'rw', traits => ['Chained'], default => 'field_layout_errors' );
has layout_label_filename =>
    ( is => 'rw', traits => ['Chained'], default => 'field_layout_label' );
has layout_field_filename =>
    ( is => 'rw', traits => ['Chained'], default => 'field_layout_field' );
has layout_comment_filename =>
    ( is => 'rw', traits => ['Chained'], default => 'field_layout_comment' );
has layout_javascript_filename =>
    ( is => 'rw', traits => ['Chained'], default => 'field_layout_javascript' );
has layout_label_text_filename =>
    ( is => 'rw', traits => ['Chained'], default => 'field_layout_label_text' );
has layout_block_filename =>
    ( is => 'rw', traits => ['Chained'], default => 'field_layout_block' );

has layout_parser_filename =>
    ( is => 'rw', traits => ['Chained'], default => 'field_layout_parser' );

has _layout => (
    is      => 'rw',
    default => sub {
        return [ 'errors', 'label', 'field', 'comment', 'javascript', ];
    },
);

# if we ever remove the reverse_single() method, we can make layout()
# a standard Moose attribute

sub layout {
    my $self = shift;

    if (@_) {
        $self->_layout(@_);
        return $self;
    }

    my $value = $self->_layout;

    if ( defined $value && $self->reverse_single ) {

        # if it's an array-ref,
        # and 'label' and 'field' are consecutive values (in any order)
        # then just swap them around
        # otherwise warn that reverse_single() is deprecated

        my ( $ok, $field_index, $label_index );

        if ( ref $value && 'ARRAY' eq reftype($value) ) {
            $field_index = first_index { 'field' eq $_ } @$value;
            $label_index = first_index { 'label' eq $_ } @$value;

            if (   defined $field_index
                && defined $label_index
                && 1 == abs( $field_index - $label_index ) )
            {
                $ok = 1;
            }
        }

        if ($ok) {

            # create new arrayref so we don't change the stored value
            $value = [@$value];

            @$value[$field_index] = 'label';
            @$value[$label_index] = 'field';
        }
        else {
            carp "reverse_single() is deprecated, and is having no affect.";
        }
    }

    return $value;
}

has _multi_layout => (
    is      => 'rw',
    default => sub {
        return [ 'label', 'field', ];
    },
);

# if we ever remove the reverse_multi() method, we can make multi_layout()
# a standard Moose attribute

sub multi_layout {
    my $self = shift;

    if (@_) {
        $self->_multi_layout(@_);
        return $self;
    }

    my $value = $self->_multi_layout;

    if ( defined $value && $self->reverse_multi ) {

        # if it's an array-ref,
        # and 'label' and 'field' are consecutive values (in any order)
        # then just swap them around
        # otherwise warn that reverse_multi() is deprecated

        my ( $ok, $field_index, $label_index );

        if ( ref $value && 'ARRAY' eq reftype($value) ) {
            $field_index = first_index { 'field' eq $_ } @$value;
            $label_index = first_index { 'label' eq $_ } @$value;

            if (   defined $field_index
                && defined $label_index
                && 1 == abs( $field_index - $label_index ) )
            {
                $ok = 1;
            }
        }

        if ($ok) {

            # create new arrayref so we don't change the stored value
            $value = [@$value];

            @$value[$field_index] = 'label';
            @$value[$label_index] = 'field';
        }
        else {
            carp "reverse_multi() is deprecated, and is having no affect.";
        }
    }

    return $value;
}

after BUILD => sub {
    my $self = shift;

    $self->filename('field_layout');

    return;
};

around render_data_non_recursive => sub {
    my ( $orig, $self, $args ) = @_;

    my $render = $self->$orig(
        {   layout                     => $self->layout,
            multi_layout               => $self->multi_layout,
            layout_errors_filename     => $self->layout_errors_filename,
            layout_label_filename      => $self->layout_label_filename,
            layout_field_filename      => $self->layout_field_filename,
            layout_comment_filename    => $self->layout_comment_filename,
            layout_javascript_filename => $self->layout_javascript_filename,
            layout_label_text_filename => $self->layout_label_text_filename,
            layout_block_filename      => $self->layout_block_filename,
            layout_parser_filename     => $self->layout_parser_filename,
            $args ? %$args : (),
        } );

    return $render;
};

sub string {
    my ( $self, $args ) = @_;

    $args ||= {};

    my $render
        = exists $args->{render_data}
        ? $args->{render_data}
        : $self->render_data;

    my $layout
        = exists $args->{layout}
        ? $args->{layout}
        : $self->layout;

    my $html = "";

    if ( defined $render->{container_tag} ) {
        $html .= sprintf "<%s%s>\n",
            $render->{container_tag},
            process_attrs( $render->{container_attributes} );
    }

    $html .= $self->_parse_layout( $render, $layout );

    if ( defined $render->{container_tag} ) {
        $html .= sprintf "\n</%s>", $render->{container_tag},;
    }

    return $html;
}

sub _parse_layout {
    my ( $self, $render, $layout ) = @_;

    croak "undefined 'layout'" if !defined $layout;

    my $html = "";

    if ( ref $layout && 'ARRAY' eq ref $layout ) {
        my @item_html;
        for my $item (@$layout) {
            push @item_html, $self->_parse_layout( $render, $item );
        }
        $html .=
            join "\n",
            grep { defined && length } @item_html;
    }
    elsif ( ref $layout && 'HASH' eq ref $layout ) {
        my ( $key, $value ) = %$layout;

        if ( my $method = $self->can("_parse_layout_$key") ) {
            $html .= $self->$method( $render, $key, $value );
        }
        else {
            $html .= $self->_parse_layout_block( $render, $key, $value );
        }
    }
    elsif ( my $method = $self->can("_parse_layout_$layout") ) {
        $html .= $self->$method($render);
    }
    else {
        croak "Unknown layout() option: '$layout'";
    }

    return $html;
}

sub _parse_layout_errors {
    my ( $self, $render ) = @_;

    return $self->_string_errors($render);
}

sub _parse_layout_label {
    my $self   = shift;
    my $render = shift;

    return ""
        unless exists $render->{label}
        && defined $render->{label}
        && length $render->{label};

    if (@_) {
        my ( $tag, @content ) = @_;

        return $self->_parse_layout_block(
            $render, $tag,
            {   attributes => $render->{label_attributes},
                content    => \@content,
            },
        );
    }
    else {
        return $self->_string_label($render);
    }
}

sub _parse_layout_field {
    my ( $self, $render ) = @_;

    return $self->_string_field($render);
}

sub _parse_layout_comment {
    my ( $self, $render ) = @_;

    return "" if !defined $render->{comment};

    my $html = sprintf "<%s%s>\n%s\n</%s>",
        'span',
        process_attrs( $render->{comment_attributes} ),
        $render->{comment},
        'span';

    return $html;
}

sub _parse_layout_javascript {
    my ( $self, $render ) = @_;

    return "" if !defined $render->{javascript};

    my $html = sprintf qq{<script type="text/javascript">\n%s\n</script>},
        $render->{javascript};

    return $html;
}

sub _parse_layout_label_text {
    my ( $self, $render ) = @_;

    return "" unless exists $render->{label} && length $render->{label};

    return $render->{label};
}

sub _parse_layout_block {
    my ( $self, $render, $tag, $opts ) = @_;

    $opts ||= {};

    my $html = "<$tag";

    if ( $opts->{attributes} ) {
        $html .= process_attrs( $opts->{attributes} );
    }

    $html .= ">\n";

    if ( $opts->{content} ) {
        $html .= $self->_parse_layout( $render, $opts->{content} );
    }

    $html .= "\n</$tag>";

    return $html;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

HTML::FormFu::Role::Element::Layout

=head1 VERSION

version 2.06

=head1 AUTHOR

Carl Franks <cpan@fireartist.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2018 by Carl Franks.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut