use 5.010001;
use strict;
use warnings;

package Mite::Trait::HasSuperclasses;
use Mite::Miteception -role, -all;

our $AUTHORITY = 'cpan:TOBYINK';
our $VERSION   = '0.012000';

# Super classes as class names
has extends =>
  is            => bare,
  accessor      => 'superclasses',
  isa           => ArrayRef[ValidClassName],
  default       => sub { [] },
  default_does_trigger => true,
  trigger       => sub {
    my $self = shift;

    return if !$self->name; # called from constructor

    # Set up our @ISA so we can use mro to calculate the class hierarchy
    $self->_set_isa;

    # Allow $self->parents to recalculate itself
    $self->_clear_parents;
  };

has superclass_args =>
  is            => rw,
  isa           => Map[ NonEmptyStr, HashRef|Undef ],
  builder       => sub { {} };

# Super classes as Mite::Classes populated from $self->superclasses
has parents =>
  is            => ro,
  isa           => ArrayRef[MiteClass],
  # Build on demand to allow the project to load all the classes first
  lazy          => true,
  builder       => '_build_parents',
  clearer       => '_clear_parents';

sub _set_isa {
    my $self = shift;

    my $name = $self->name;

    mro::set_mro($name, "c3");
    no strict 'refs';
    @{$name.'::ISA'} = @{$self->superclasses};

    return;
}

sub get_isa {
    my $self = shift;

    my $name = $self->name;

    no strict 'refs';
    return @{$name.'::ISA'};
}

sub linear_isa {
    my $self = shift;

    return @{mro::get_linear_isa($self->name)};
}

sub linear_parents {
    my $self = shift;

    my $project = $self->project;

    return grep defined, map { $project->class($_) } $self->linear_isa;
}

sub handle_extends_keyword {
    my $self = shift;

    my ( @extends, %extends_args );
    while ( @_ ) {
        my $class = shift;
        my $args  = Str->check( $_[0] ) ? undef : shift;
        push @extends, $class;
        $extends_args{$class} = $args;
    }
    $self->superclasses( \@extends );
    $self->superclass_args( \%extends_args );

    return;
}

sub _build_parents {
    my $self = shift;

    my $extends = $self->superclasses;
    return [] if !@$extends;

    # Load each parent and store its Mite::Class
    my @parents;
    for my $parent_name (@$extends) {
        push @parents, $self->_get_parent($parent_name);
    }

    return \@parents;
}

sub _get_parent {
    my ( $self, $parent_name ) = ( shift, @_ );

    my $project = $self->project;

    # See if it's already loaded
    my $parent = $project->class($parent_name);
    return $parent if $parent;

    # If not, try to load it
    eval "require $parent_name;";
    $parent = $project->class($parent_name);
    return $parent if $parent;

    return;
}

before inject_mite_functions => sub {
    my ( $self, $file, $arg ) = ( shift, @_ );

    my $requested = sub { $arg->{$_[0]} ? 1 : $arg->{'!'.$_[0]} ? 0 : $arg->{'-all'} ? 1 : $_[1]; };
    my $defaults  = ! $arg->{'!-defaults'};
    my $shim      = $self->shim_name;
    my $package   = $self->name;
    my $fake_ns   = $self->project->can('_module_fakeout_namespace') && $self->project->_module_fakeout_namespace;

    no strict 'refs';

    if ( $requested->( 'extends', $defaults ) ) {

        *{ $package .'::extends' } = sub {
            return $self->handle_extends_keyword(
                defined( $fake_ns )
                    ? map Str->check($_) ? "$fake_ns\::$_" : $_, @_
                    : @_
            );
        };

        $self->imported_keywords->{'extends'} = 'sub {}';
    }
};

around compilation_stages => sub {
    my ( $next, $self ) = ( shift, shift );
    my @stages = $self->$next( @_ );
    push @stages, qw( _compile_extends );
    return @stages;
};

around _compile_meta_method => sub {
    my ( $next, $self ) = ( shift, shift );

    # Check if we are inheriting from a Mite class in this project
    my $inherit_from_mite = do {
        # First parent
        my $first_isa = do {
            my @isa = $self->linear_isa;
            shift @isa;
            shift @isa;
        };
        !! ( $first_isa and $self->_get_parent( $first_isa ) );
    };

    return '' if $inherit_from_mite;

    return $self->$next( @_ );
};

sub _compile_extends {
    my $self = shift;

    my $extends = $self->superclasses;
    return '' unless @$extends;

    my $source = $self->source;

    my $require_list = join "\n\t",
        map  { "require $_;" }
        # Don't require a class from the same source
        grep { !$source || !$source->has_class($_) }
        @$extends;

    my $version_tests = join "\n\t",
        map { sprintf '%s->VERSION( %s );',
            B::perlstring( $_ ),
            B::perlstring( $self->superclass_args->{$_}{'-version'} )
        }
        grep {
            $self->superclass_args->{$_}
            and $self->superclass_args->{$_}{'-version'}
        }
        @$extends;

    my $isa_list = join ", ", map B::perlstring($_), @$extends;

    return <<"END";
BEGIN {
    $require_list
    $version_tests
    use mro 'c3';
    our \@ISA;
    push \@ISA, $isa_list;
}
END
}

around _compile_mop_postamble => sub {
    my ( $next, $self ) = ( shift, shift );
    my $code = $self->$next( @_ );

    my @superclasses = @{ $self->superclasses || [] }
        or return $code;
    $code .= sprintf "Moose::Util::find_meta( %s )->superclasses( %s );\n",
        B::perlstring( $self->name ),
        join q{, }, map B::perlstring( $_ ), @superclasses;

    return $code;
};

1;