package DX::Step::ResolveProposition;

use DX::Step::EnterRecheck;
use DX::Step::Backtrack;

use Types::Standard qw(ArrayRef);
use DX::Utils qw(deparse);

use DX::Class;

with 'DX::Role::Step';

has actions => (is => 'ro', isa => ArrayRef[Action], required => 1);

#has depends_on => (is => 'ro', isa => DependencyGroupList, required => 1);

has depends_on => (is => 'ro', required => 1, coerce => sub {
  my ($dep_groups) = @_;
  my @exp;
  assert_DependencyGroupList $dep_groups;
  [ map {
    my ($on, @deps) = @$_;
    my @exp;
    DEP: foreach my $dep (@deps) {
      my ($type, @path) = @$dep;
      push @exp, [
        $type,
        map { ref() ? @{$_->identity_path or next DEP} : $_ } @path
      ];
    }
    (@exp ? [ $on, @exp ] : ());
  } @$dep_groups ];
});

has resolves => (is => 'ro', isa => Proposition);

has alternative_step => (is => 'ro', isa => Step);

sub but_first {
  my ($self, @actions) = @_;
  $self->but(actions => [ @actions, @{$self->actions} ]);
}

sub but_with_dependencies_on {
  my ($self, @deps) = @_;
  $self->but(depends_on => [ @{$self->depends_on}, @deps ]);
}

sub apply_to {
  my ($self, $old_ss) = @_;
  my $ns = do {
    if (my $prop = $old_ss->next_proposition) {
      DX::Step::ConsiderProposition->new(
        proposition => $prop
      )
    } else {
      $old_ss->on_solution_step
    }
  };
  my $alt_step = $self->alternative_step;
  my $ss = $old_ss->but(
    next_step => $ns,
    ($alt_step
      ? (alternatives => [
          [ $old_ss->current_hypothesis, $alt_step ],
          @{$old_ss->alternatives}
        ])
      : ()
    ),
  );
  my $new_ss = $self->_apply_to_ss($ss);
  return $ss->but(next_step => DX::Step::Backtrack->new) unless $new_ss;
  return $new_ss;
}

sub _apply_to_ss {
  my ($self, $old_ss) = @_;
  my $old_hyp = $old_ss->current_hypothesis;
  (my $hyp, my @recheck) = $old_hyp->with_resolution(
    $self->resolves, $self->depends_on, $self->actions
  );
  return undef unless $hyp;
  return $self->_recheck_for(
    $old_ss->but(current_hypothesis => $hyp),
    @recheck
  );
}

sub _recheck_for {
  my ($self, $old_ss, @recheck) = @_;

  return $old_ss unless @recheck;

  my $ss = $old_ss->but(
    next_step => DX::Step::EnterRecheck->new(
      proposition_list => \@recheck,
      on_completion_step => $old_ss->next_step,
      on_failure_step => DX::Step::Backtrack->new,
    ),
  );

  return $ss;
}

1;
