package App::Clifton::ConsoleService::Session;

use Eval::WithLexicals;
use Data::Dumper::Concise;
use Moo;

extends 'IO::Async::Protocol::LineStream';

has lex_env => (is => 'lazy');

sub _build_lex_env { Eval::WithLexicals->new }

sub on_read_line {
  my ($self, $line) = @_;
  my ($cmd, $body) = split(/ /, $line, 2);
  if ($cmd !~ /^([A-Z]+)$/) {
    $self->_send_response(ERROR => "Invalid command ${cmd}");
  } elsif (my $call = $self->can(lc "handle_${cmd}")) {
    $self->$call($body ? eval $body : ());
  } else {
    $self->_send_response(ERROR => "Unknown command ${cmd}");
  }
}

sub _send_response {
  my ($self, $code, $body) = @_;

  if (my $ref = ref($body)) {
    ($code, $body) = (ERROR => "Command returned body of reftype ${ref}");
  }
  if ($code !~ /^([A-Z]+)$/) {
    ($code, $body) = (ERROR => "Command returned malformed code ${code}");
  }

  chomp(my $enc_body = Dumper $body);

  $self->write_line(join ' ', $code, $enc_body);
}

sub handle_echo {
  my ($self, $body) = @_;
  $self->_send_response(ECHO => $body);
}

sub handle_eval {
  my ($self, $body) = @_;
  return $self->_send_response(ERROR => 'You asked me to eval undef!')
    unless defined $body;
  if (my $ref = ref $body) {
    return $self->_send_response(
      ERROR => "You asked me to eval a ${ref} ref"
    );
  }
  my $eval = $self->lex_env;
  my $code = 'RESULT';
  my @ret; local $@; eval {
    @ret = $eval->eval($body); 1;
  } or ($code, @ret) = (ERROR => $@);
  $self->_send_response(
    $code => ($code eq 'RESULT'
                ? do { local $Data::Dumper::Maxdepth = 1; Dumper @ret }
                : $ret[0]
             )
  );
}

sub on_closed {
  my ($self) = @_;
  $self->parent->remove_child($self);
}

1;
