# frozen_string_literal: true

require "readline" unless defined?(Readline)
require "pry/input_completer"

def completer_test(bind, pry = nil, assert_flag = true)
  test = proc do |symbol|
    input = pry || Readline
    input_completer = Pry::InputCompleter.new(input, pry)
    completions = input_completer.call(symbol[0..-2], target: Pry.binding_for(bind))
    expect(completions.include?(symbol)).to eq(assert_flag)
  end
  proc { |*symbols| symbols.each(&test) }
end

describe Pry::InputCompleter do
  before do
    # The AMQP gem has some classes like this:
    #  pry(main)> AMQP::Protocol::Test::ContentOk.name
    #  => :content_ok
    module SymbolyName
      def self.name
        :symboly_name
      end
    end

    @before_completer = Pry.config.completer
    Pry.config.completer = Pry::InputCompleter
  end

  after do
    Pry.config.completer = @before_completer
    Object.remove_const :SymbolyName
  end

  it "should not crash if there's a Module that has a symbolic name." do
    skip unless Pry::Helpers::Platform.jruby?
    expect do
      Pry::InputCompleter.new(Readline).call(
        "a.to_s.", target: Pry.binding_for(Object.new)
      )
    end.not_to raise_error
  end

  it 'should take parenthesis and other characters into account for symbols' do
    expect do
      Pry::InputCompleter.new(Readline).call(
        ":class)", target: Pry.binding_for(Object.new)
      )
    end.not_to raise_error
  end

  it 'should complete instance variables' do
    object = Class.new.new

    # set variables in appropriate scope
    object.instance_variable_set(:'@name', 'Pry')
    object.class.send(:class_variable_set, :'@@number', 10)

    # check to see if variables are in scope
    expect(object.instance_variables
      .map(&:to_sym)
      .include?(:'@name')).to eq true

    expect(object.class.class_variables
      .map(&:to_sym)
      .include?(:'@@number')).to eq true

    # Complete instance variables.
    b = Pry.binding_for(object)
    completer_test(b).call('@name', '@name.downcase')

    # Complete class variables.
    b = Pry.binding_for(object.class)
    completer_test(b).call('@@number', '@@number.class')
  end

  it 'should complete for stdlib symbols' do
    o = Object.new
    # Regexp
    completer_test(o).call('/foo/.extend')

    # Array
    completer_test(o).call('[1].push')

    # Hash
    completer_test(o).call('{"a" => "b"}.keys')

    # Proc
    completer_test(o).call('{2}.call')

    # Symbol
    completer_test(o).call(':symbol.to_s')

    # Absolute Constant
    completer_test(o).call('::IndexError')
  end

  it 'should complete for target symbols' do
    o = Object.new

    # Constant
    module Mod
      remove_const :CON if defined? CON
      CON = 'Constant'.freeze
      module Mod2
      end
    end

    completer_test(Mod).call('CON')

    # Constants or Class Methods
    completer_test(o).call('Mod::CON')

    # Symbol
    _foo = :symbol
    completer_test(o).call(':symbol')

    # Variables
    class << o
      attr_accessor :foo
    end
    o.foo = 'bar'
    completer_test(binding).call('o.foo')

    # trailing slash
    expect(Pry::InputCompleter.new(Readline).call('Mod2/', target: Pry.binding_for(Mod))
      .include?('Mod2/')).to eq(true)
  end

  it 'should complete for arbitrary scopes' do
    module Bar
      @barvar = :bar
    end

    module Baz
      remove_const :CON if defined? CON
      @bar = Bar
      @bazvar = :baz
      CON = :constant
    end

    pry = Pry.new(target: Baz)
    pry.push_binding(Bar)

    b = Pry.binding_for(Bar)
    completer_test(b, pry).call("../@bazvar")
    completer_test(b, pry).call('/CON')
  end

  it 'should complete for stdlib symbols' do
    o = Object.new
    # Regexp
    completer_test(o).call('/foo/.extend')

    # Array
    completer_test(o).call('[1].push')

    # Hash
    completer_test(o).call('{"a" => "b"}.keys')

    # Proc
    completer_test(o).call('{2}.call')

    # Symbol
    completer_test(o).call(':symbol.to_s')

    # Absolute Constant
    completer_test(o).call('::IndexError')
  end

  it 'should complete for target symbols' do
    o = Object.new

    # Constant
    module Mod
      remove_const :CON if defined? CON
      CON = 'Constant'.freeze
      module Mod2
      end
    end

    completer_test(Mod).call('CON')

    # Constants or Class Methods
    completer_test(o).call('Mod::CON')

    # Symbol
    _foo = :symbol
    completer_test(o).call(':symbol')

    # Variables
    class << o
      attr_accessor :foo
    end
    o.foo = 'bar'
    completer_test(binding).call('o.foo')

    # trailing slash
    expect(Pry::InputCompleter.new(Readline).call('Mod2/', target: Pry.binding_for(Mod))
      .include?('Mod2/')).to eq(true)
  end

  it 'should complete for arbitrary scopes' do
    module Bar
      @barvar = :bar
    end

    module Baz
      remove_const :CON if defined? CON
      @bar = Bar
      @bazvar = :baz
      CON = :constant
    end

    pry = Pry.new(target: Baz)
    pry.push_binding(Bar)

    b = Pry.binding_for(Bar)
    completer_test(b, pry).call("../@bazvar")
    completer_test(b, pry).call('/CON')
  end

  it 'should not return nil in its output' do
    pry = Pry.new
    expect(Pry::InputCompleter.new(Readline, pry).call("pry.", target: binding))
      .not_to include nil
  end

  it 'completes expressions with all available methods' do
    completer_test(self).call("[].size.chars")
  end

  it 'does not offer methods from restricted modules' do
    require 'irb'
    completer_test(self, nil, false).call("[].size.parse_printf_format")
  end

  unless Pry::Helpers::Platform.jruby?
    # Classes that override .hash are still hashable in JRuby, for some reason.
    it 'ignores methods from modules that override Object#hash incompatibly' do
      require 'irb'

      m = Module.new do
        def self.hash; end

        def aaaa; end
      end

      completer_test(m, nil, false).call("[].size.aaaa")
    end
  end
end
