# frozen_string_literal: true

RSpec.describe Pry::CLI do
  before { described_class.reset }

  describe ".add_options" do
    it "returns self" do
      expect(described_class.add_options).to eq(described_class)
    end

    context "when options is nil and a block is provided" do
      before { described_class.options = nil }

      it "sets the block as options" do
        block = proc {}
        described_class.add_options(&block)
        expect(described_class.options).to eql(block)
      end
    end

    context "when options were previously set" do
      it "overwrites the options proc that executes the provided block" do
        described_class.options = proc {}

        executed = false
        described_class.add_options { executed = true }

        described_class.options.call
        expect(executed).to be_truthy
      end

      it "overwrites the options proc that executes original options" do
        original_executed = false
        described_class.options = proc { original_executed = true }

        described_class.add_options {}
        described_class.options.call

        expect(original_executed).to be_truthy
      end
    end
  end

  describe ".add_option_processor" do
    it "returns self" do
      expect(described_class.add_option_processor {}).to eq(described_class)
    end

    it "adds an option processor" do
      option_processor = proc {}
      described_class.add_option_processor(&option_processor)
      expect(described_class.option_processors).to eql([option_processor])
    end
  end

  describe ".parse_options" do
    context "when option exists" do
      before { described_class.options = proc { on(:v, 'test') } }

      it "removes the existing option from ARGV" do
        argv = %w[filename -v]
        described_class.parse_options(argv)
        expect(argv).not_to include('-v')
      end

      it "initializes session setup" do
        expect(Pry).to receive(:initial_session_setup)
        described_class.parse_options(%w[-v])
      end

      it "finalizes session setup" do
        expect(Pry).to receive(:final_session_setup)
        described_class.parse_options(%w[-v])
      end
    end

    context "when multiple options exist" do
      it "processes only called options" do
        processor_a_called = false
        processor_b_called = false
        processor_c_called = false
        described_class.options = proc do
          on('option-a', 'test a') { processor_a_called = true }
          on('option-b', 'test b') { processor_b_called = true }
          on('option-c', 'test c') { processor_c_called = true }
        end

        described_class.parse_options(%w[--option-a --option-b])

        expect(processor_a_called).to be_truthy
        expect(processor_b_called).to be_truthy
        expect(processor_c_called).to be_falsey
      end
    end

    context "when option doesn't exist" do
      it "raises error" do
        expect { described_class.parse_options(['--nothing']) }
          .to raise_error(Pry::CLI::NoOptionsError)
      end
    end

    context "when argv is passed with a dash (-)" do
      before { described_class.options = proc {} }

      it "sets everything after the dash as input args" do
        argv = %w[filename - foo bar]
        described_class.parse_options(argv)
        expect(described_class.input_args).to eq(%w[foo bar])
      end
    end

    context "when argv is passed with a double dash (--)" do
      before { described_class.options = proc {} }

      it "sets everything after the double dash as input args" do
        argv = %w[filename -- foo bar]
        described_class.parse_options(argv)
        expect(described_class.input_args).to eq(%w[foo bar])
      end
    end

    context "when invalid option is provided" do
      before { described_class.options = proc { on(:valid, 'valid') } }

      it "exits program" do
        expect(Kernel).to receive(:exit)
        expect(STDOUT).to receive(:puts)

        described_class.parse_options(%w[--invalid])
      end
    end
  end

  describe ".start" do
    before do
      # Don't start Pry session in the middle of tests.
      allow(Pry).to receive(:start)

      described_class.options = proc {}
    end

    it "sets Pry.cli to true" do
      opts = described_class.parse_options(%w[])
      described_class.start(opts)
      expect(Pry.cli).to be_truthy
    end

    context "when the help option is provided" do
      before { described_class.options = proc { on(:help, 'help') } }

      it "exits" do
        expect(Kernel).to receive(:exit)

        opts = described_class.parse_options(%w[--help])
        described_class.start(opts)
      end
    end

    context "when the context option is provided" do
      before { described_class.options = proc { on(:context=, 'context') } }

      it "initializes session setup" do
        expect(Pry).to receive(:initial_session_setup).twice
        opts = described_class.parse_options(%w[--context=Object])
        described_class.start(opts)
      end

      it "finalizes session setup" do
        expect(Pry).to receive(:final_session_setup).twice
        opts = described_class.parse_options(%w[--context=Object])
        described_class.start(opts)
      end

      it "starts Pry in the provided context" do
        expect(Pry).to receive(:start).with(
          instance_of(Binding), input: instance_of(StringIO)
        ) do |binding, _opts|
          expect(binding.eval('self')).to be_an(Object)
        end
        opts = described_class.parse_options(%w[--context=Object])
        described_class.start(opts)
      end
    end

    context "when the context option is not provided" do
      before { described_class.options = proc {} }

      it "starts Pry in the top level" do
        expect(Pry).to receive(:start).with(
          instance_of(Binding), input: instance_of(StringIO)
        ) do |binding, _opts|
          expect(binding.eval('self')).to eq(Pry.main)
        end
        opts = described_class.parse_options(%w[])
        described_class.start(opts)
      end
    end

    context "when there are some input args" do
      before { described_class.options = proc {} }

      it "loads files through repl and exits" do
        expect(Pry).to receive(:load_file_through_repl).with(match(%r{/foo}))
        expect(Kernel).to receive(:exit)

        opts = described_class.parse_options(%w[foo])
        described_class.start(opts)
      end
    end

    context "when 'pry' is passed as an input arg" do
      before { described_class.options = proc {} }

      it "does not load files through repl" do
        expect(Pry).not_to receive(:load_file_through_repl)

        opts = described_class.parse_options(%w[pry])
        described_class.start(opts)
      end
    end
  end
end
