class Polars::Slice

@private

def _as_original(lazy, original)

Return lazy variant back to its original type.
def _as_original(lazy, original)
  frame = lazy.collect
  original.is_a?(DataFrame) ? frame : frame.to_series
end

def _lazify(obj)

Make lazy to ensure efficient/consistent handling.
def _lazify(obj)
  obj.is_a?(DataFrame) ? obj.lazy : obj.to_frame.lazy
end

def _slice_negative(obj)

Logic for slices with negative stride.
def _slice_negative(obj)
  stride = @stride.abs
  lazyslice = obj.slice(@stop + 1, @slice_length).reverse
  stride > 1 ? lazyslice.take_every(stride) : lazyslice
end

def _slice_positive(obj)

Logic for slices with positive stride.
def _slice_positive(obj)
  # note: at this point stride is guaranteed to be > 1
  obj.slice(@start, @slice_length).take_every(@stride)
end

def _slice_setup(s)

Normalize slice bounds, identify unbounded and/or zero-length slices.
def _slice_setup(s)
  # can normalize slice indices as we know object size
  obj_len = @obj.length
  start = if s.begin
    if s.begin < 0
      [s.begin + obj_len, 0].max
    else
      s.begin
    end
  else
    0
  end
  stop = if s.end
    if s.end < 0
      s.end + (s.exclude_end? ? 0 : 1) + obj_len
    else
      s.end + (s.exclude_end? ? 0 : 1)
    end
  else
    obj_len
  end
  stride = 1
  # check if slice is actually unbounded
  if stride >= 1
    @is_unbounded = start <= 0 && stop >= obj_len
  else
    @is_unbounded = stop == -1 && start >= obj_len - 1
  end
  # determine slice length
  if @obj.is_empty
    @slice_length = 0
  elsif @is_unbounded
    @slice_length = obj_len
  else
    @slice_length = if start == stop || (stride > 0 && start > stop) || (stride < 0 && start < stop)
      0
    else
      (stop - start).abs
    end
  end
  @start = start
  @stop = stop
  @stride = stride
end

def apply(s)

Apply a slice operation, taking advantage of any potential fast paths.
def apply(s)
  # normalize slice
  _slice_setup(s)
  # check for fast-paths / single-operation calls
  if @slice_length == 0
    @obj.clear
  elsif @is_unbounded && [-1, 1].include?(@stride)
    @stride < 0 ? @obj.reverse : @obj.clone
  elsif @start >= 0 && @stop >= 0 && @stride == 1
    @obj.slice(@start, @slice_length)
  elsif @stride < 0 && @slice_length == 1
    @obj.slice(@stop + 1, 1)
  else
    # multi-operation calls; make lazy
    lazyobj = _lazify(@obj)
    sliced = @stride > 0 ? _slice_positive(lazyobj) : _slice_negative(lazyobj)
    _as_original(sliced, @obj)
  end
end

def initialize(obj)

def initialize(obj)
  @obj = obj
end