package Aniki::Result::Collection::Joined;
use 5.014002;

use namespace::autoclean;
use Mouse v2.4.5;
extends qw/Aniki::Result::Collection/;

use Carp qw/croak/;
use Aniki::Row::Joined;
use List::MoreUtils qw/none/;
use List::UtilsBy qw/uniq_by/;
use Scalar::Util qw/refaddr/;

has '+table_name' => (
    required => 0,
    lazy     => 1,
    default  => sub { join ',', @{ $_[0]->table_names } }
);

has '+row_class' => (
    lazy    => 1,
    default => sub { croak 'Cannot get row class of '.__PACKAGE__.'. Use row_classes instead of row_class.' },
);

has table_names => (
    is       => 'ro',
    required => 1,
);

has _compact_row_datas => (
    is      => 'ro',
    lazy    => 1,
    builder => '_compress',
);

has _subresult_cache => (
    is      => 'ro',
    default => sub {
        my $self = shift;
        return +{
            map { $_ => undef } @{ $self->table_names },
        };
    },
);

sub row_classes {
    my $self = shift;
    return map { $self->handler->guess_row_class($_) } @{ $self->table_names };
}

sub rows {
    my $self = shift;
    if (@_ == 1) {
        my $table_name = shift;
        return $self->subresult($table_name)->rows();
    }
    return $self->SUPER::rows();
}

sub subresult {
    my ($self, $table_name) = @_;
    return $self->_subresult_cache->{$table_name} if $self->_subresult_cache->{$table_name};

    my $result_class = $self->handler->guess_result_class($table_name);
    return $self->_subresult_cache->{$table_name} = $result_class->new(
        table_name           => $table_name,
        handler              => $self->handler,
        row_datas            => [uniq_by { refaddr $_ } map { $_->{$table_name} } @{ $self->_compact_row_datas() }],
        !$self->suppress_row_objects ? (
            inflated_rows    => [uniq_by { refaddr $_ } map { $_->$table_name   } @{ $self->inflated_rows() }],
        ) : (),
        suppress_row_objects => $self->suppress_row_objects,
    );
}

sub _uniq_key {
    my ($row_data, $pk) = @_;
    return if none { defined $row_data->{$_} } @$pk;
    return join '|', map { quotemeta $row_data->{$_} } @$pk;
}

sub _compress {
    my $self = shift;
    my $handler = $self->handler;

    my @table_names = @{ $self->table_names };
    my %pk = map {
        $_ => [map { $_->name }  $handler->schema->get_table($_)->primary_key->fields]
    } @table_names;

    my @rows;
    my %cache;
    for my $row (@{ $self->row_datas }) {
        my %rows;

        for my $table_name (@table_names) {
            my $row_data = $row->{$table_name};
            my $uniq_key = _uniq_key($row_data, $pk{$table_name});
            $rows{$table_name} = defined $uniq_key ? ($cache{$table_name}{$uniq_key} //= $row_data) : $row_data;
        }

        push @rows => \%rows;
    }

    return \@rows;
}

sub _inflate {
    my $self = shift;
    my $handler = $self->handler;

    my @table_names = @{ $self->table_names };
    my %row_class = map { $_ => $handler->guess_row_class($_) } @table_names;

    my @rows;
    my %cache;
    for my $row (@{ $self->_compact_row_datas }) {
        my %rows;

        # inflate to row class
        for my $table_name (@table_names) {
            my $row_data = $row->{$table_name};
            $rows{$table_name} = $cache{$table_name}{refaddr $row_data} //= $row_class{$table_name}->new(
                table_name => $table_name,
                handler    => $handler,
                row_data   => $row_data,
            );
        }

        push @rows => Aniki::Row::Joined->new(values %rows);
    }

    return \@rows;
}

__PACKAGE__->meta->make_immutable();
__END__