package Aniki::Row {
    use namespace::sweep;
    use Mouse v2.4.5;
    use Carp qw/croak/;

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

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

    has is_new => (
        is      => 'rw',
        default => 0,
    );

    has relay_data => (
        is      => 'ro',
        default => sub { +{} },
    );

    my %handler;

    sub BUILD {
        my ($self, $args) = @_;
        $handler{0+$self} = delete $args->{handler};
    }

    sub handler { $handler{0+shift} }
    sub schema  { shift->handler->schema }
    sub filter  { shift->handler->filter }

    sub table {
        my $self = shift;
        return $self->handler->schema->get_table($self->table_name);
    }

    sub get {
        my ($self, $column) = @_;
        return $self->{__instance_cache}{get}{$column} if exists $self->{__instance_cache}{get}{$column};

        return undef unless exists $self->row_data->{$column}; ## no critic

        my $data = $self->get_column($column);
        return $self->{__instance_cache}{get}{$column} = $self->filter->inflate_column($self->table_name, $column, $data);
    }

    sub relay {
        my ($self, $key) = @_;
        unless (exists $self->relay_data->{$key}) {
            $self->relay_data->{$key} = $self->relay_fetch($key);
        }

        my $relay_data = $self->relay_data->{$key};
        return unless defined $relay_data;
        return wantarray ? @$relay_data : $relay_data if ref $relay_data eq 'ARRAY';
        return $relay_data;
    }

    sub relay_fetch {
        my ($self, $key) = @_;
        $self->handler->fetch_and_attach_relay_data($self->table_name, [$key], [$self]);
        return $self->relay_data->{$key};
    }

    sub get_column {
        my ($self, $column) = @_;
        return undef unless exists $self->row_data->{$column}; ## no critic
        return $self->row_data->{$column};
    }

    sub get_columns {
        my $self = shift;

        my %row;
        for my $column (keys %{ $self->row_data }) {
            $row{$column} = $self->row_data->{$column};
        }
        return \%row;
    }

    sub refetch {
        my ($self, $opts) = @_;
        $opts //= +{};
        $opts->{limit} = 1;

        my $where = $self->handler->_where_row_cond($self->table, $self->row_data);
        return $self->handler->select($self->table_name => $where, $opts)->first;
    }

    my %accessor_method_cache;
    sub _accessor_method_cache {
        my $self = shift;
        return $accessor_method_cache{$self->table_name} //= {};
    }

    sub _guess_accessor_method {
        my ($invocant, $method) = @_;

        if (ref $invocant) {
            my $self   = $invocant;
            my $column = $method;

            my $cache = $self->_accessor_method_cache();
            return $cache->{$column} if exists $cache->{$column};

            return $cache->{$column} = sub { shift->get($column) } if exists $self->row_data->{$column};

            my $relationships = $self->table->get_relationships;
            return $cache->{$column} = sub { shift->relay($column) } if $relationships && $relationships->get($column);
        }

        return undef; ## no critic
    }

    sub can {
        my ($invocant, $method) = @_;
        my $code = $invocant->SUPER::can($method);
        return $code if defined $code;
        return $invocant->_guess_accessor_method($method);
    }

    our $AUTOLOAD;
    sub AUTOLOAD {
        my $invocant = shift;
        my $column = $AUTOLOAD =~ s/^.+://r;

        if (ref $invocant) {
            my $self = $invocant;
            my $method = $self->_guess_accessor_method($column);
            return $self->$method(@_) if defined $method;
        }

        my $msg = sprintf q{Can't locate object method "%s" via package "%s"}, $column, ref $invocant || $invocant;
        croak $msg;
    }

    sub DEMOLISH {
        my $self = shift;
        delete $handler{0+$self};
    }

    __PACKAGE__->meta->make_immutable();
};

1;

=pod

=encoding utf-8

=head1 NAME

Aniki::Row - Row class

=head1 SYNOPSIS

    my $result = $db->select(foo => { bar => 1 });
    for my $row ($result->all) {
        print $row->id, "\n";
    }

=head1 DESCRIPTION

This is row class.

=head1 INSTANCE METHODS

=head2 C<$column()>

Autoload column name method to C<< $row->get($column) >>.

=head2 C<$relay()>

Autoload relationship name method to C<< $row->relay($column) >>.

=head2 C<get($column)>

Returns column data.

=head2 C<relay($name)>

Returns related data.
If not yet cached, call C<relay_fetch>.

=head2 C<relay_fetch($name)>

Fetch related data, and returns related data.

=head2 C<get_column($column)>

Returns column data without inflate filters.

=head2 C<get_columns()>

Returns columns data as hash reference.

=head2 C<refetch()>

=head1 ACCESSORS

=over 4

=item C<handler : Aniki>

=item C<schema : Aniki::Schema>

=item C<table : Aniki::Schema::Table>

=item C<filter : Aniki::Filter>

=item C<table_name : Str>

=item C<is_new : Bool>

=item C<row_data : HashRef>

=item C<relay_data : HashRef>

=back

=head1 LICENSE

Copyright (C) karupanerura.

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

=head1 AUTHOR

karupanerura E<lt>karupa@cpan.orgE<gt>

=cut
