package Data::Query::Renderer::SQL::Slice::FetchFirst;

use Data::Query::ExprHelpers;
use Moo::Role;

sub _render_slice_limit {
  my ($self, $dq) = @_;
  return [
    ($dq->{from} ? $self->_render($dq->{from}) : ()),
    $self->_format_keyword('FETCH FIRST'),
    sprintf("%i", $dq->{limit}{value}),
    $self->_format_keyword('ROWS ONLY')
  ];
}

sub _slice_type { 'FetchFirst' }

sub _render_slice {
  my ($self, $dq) = @_;
  unless ($dq->{offset}) {
    return $self->_render_slice_limit($dq);
  }
  unless ($dq->{order_is_stable}) {
    die $self->_slice_type." limit style requires a stable order";
  }
  die "Slice's inner is not a Select"
    unless is_Select my $orig_select = $dq->{from};
  die "Slice's Select not followed by Order but order_is_stable set"
    unless is_Order $orig_select->{from};

  my $gensym_count;
  my $default_inside_alias;

  my @inside_select_list = map {
    if (is_Alias) {
      $_;
    } elsif (is_Identifier) {
      my @el = @{$_->{elements}};
      if (@el == 2 and $el[0] eq ($default_inside_alias ||= $el[0])) {
        $_;
      } else {
        Alias(join('__', @el), $_);
      }
    } else {
      Alias(sprintf("GENSYM__%03i",++$gensym_count), $_);
    }
  } @{$orig_select->{select}};

  my %alias_map = map {
    if (is_Alias and is_Identifier $_->{from}) {
      +(join('.',@{$_->{from}{elements}}) => Identifier($_->{to}))
    } elsif (is_Identifier) {
      +(join('.',@{$_->{elements}}) => $_)
    } else {
      +()
    }
  } @inside_select_list;

  my @outside_select_list = map {
    if (is_Alias) {
      Identifier($_->{to});
    } else {
      $_;
    }
  } @inside_select_list;

  my @order_nodes;
  my $inner_body = do {
    my $order = $orig_select->{from};
    while (is_Order $order) {
      push @order_nodes, $order;
      $order = $order->{from};
    }
    $order;
  };

  my $order_gensym_count;
  my @mapped_order = map {
    my $by = $_->{by};
    if (is_Identifier $by) {
      $default_inside_alias ||= $by->{elements}[0]
        if @{$by->{elements}} == 2;
      my $mapped_by
        = $alias_map{join('.', @{$by->{elements}})}
          ||= do {
                if (
                  @{$by->{elements}} == 2
                  and $by->{elements}[0] eq $default_inside_alias
                ) {
                  $by;
                } else {
                  my $name = sprintf("ORDER__BY__%03i",++$order_gensym_count);
                  push @inside_select_list, Alias($name, $by);
                  Identifier($name);
                }
              };
      Order($mapped_by, $_->{reverse});
    } else {
      die "XXX not implemented yet";
    }
  } @order_nodes;

  $default_inside_alias ||= 'me';

  my $limit_plus_offset = +{
    %{$dq->{limit}}, value => $dq->{limit}{value} + $dq->{offset}{value}
  };

  return $self->_render(
    map {
      $dq->{preserve_order}
        ? Select(
          \@outside_select_list,
          compose {
            Order($b->{by}, $b->{reverse}, $a)
          } (
            @mapped_order,
            Alias($default_inside_alias, $_)
          )
        )
        : $_
    } (
      Slice(
        undef, $dq->{limit},
        Select(
          [
            @outside_select_list,
            $dq->{preserve_order}
              ? (grep @{$_->{elements}} == 1,
                  map $_->{by}, @mapped_order)
              : (),
          ],
          compose {
            Order($b->{by}, !$b->{reverse}, $a)
          } (
            @mapped_order,
            Alias(
              $default_inside_alias,
              Slice(
                undef, $limit_plus_offset,
                Select(
                  \@inside_select_list,
                  compose {
                    Order($b->{by}, $b->{reverse}, $a)
                  } @order_nodes, $inner_body
                )
              )
            )
          )
        )
      )
    )
  );
}

1;
