# frozen_string_literal: true

require 'cucumber/cucumber_expressions/cucumber_expression_generator'
require 'cucumber/cucumber_expressions/cucumber_expression'
require 'cucumber/cucumber_expressions/parameter_type'
require 'cucumber/cucumber_expressions/parameter_type_registry'

module Cucumber
  module CucumberExpressions
    describe CucumberExpressionGenerator do
      class Currency
      end

      before do
        @parameter_type_registry = ParameterTypeRegistry.new
        @generator = described_class.new(@parameter_type_registry)
      end

      it 'documents expression generation' do
        parameter_registry = ParameterTypeRegistry.new
        ### [generate-expression]
        generator = described_class.new(parameter_registry)
        undefined_step_text = 'I have 2 cucumbers and 1.5 tomato'
        generated_expression = generator.generate_expressions(undefined_step_text)[0]
        expect(generated_expression.source).to eq('I have {int} cucumbers and {float} tomato')
        expect(generated_expression.parameter_types[1].type).to eq(Float)
        ### [generate-expression]
      end

      it 'generates expression for no args' do
        assert_expression('hello', [], 'hello')
      end

      it 'generates expression with escaped left parenthesis' do
        assert_expression('\\(iii)', [], '(iii)')
      end

      it 'generates expression with escaped left curly brace' do
        assert_expression('\\{iii}', [], '{iii}')
      end

      it 'generates expression with escaped slashes' do
        assert_expression('The {int}\\/{int}\\/{int} hey', ['int', 'int2', 'int3'], 'The 1814/05/17 hey')
      end

      it 'generates expression for int float arg' do
        assert_expression('I have {int} cukes and {float} euro', ['int', 'float'], 'I have 2 cukes and 1.5 euro')
      end

      it 'generates expression for strings' do
        assert_expression('I like {string} and {string}', ['string', 'string2'], 'I like "bangers" and \'mash\'')
      end

      it 'generates expression with % sign' do
        assert_expression('I am {int}% foobar', ['int'], 'I am 20% foobar')
      end

      it 'generates expression for just int' do
        assert_expression('{int}', ['int'], '99999')
      end

      it 'numbers only second argument when builtin type is not reserved keyword' do
        assert_expression('I have {int} cukes and {int} euro', ['int', 'int2'], 'I have 2 cukes and 5 euro')
      end

      it 'numbers only second argument when type is not reserved keyword' do
        @parameter_type_registry.define_parameter_type(
          ParameterType.new(
            'currency',
            '[A-Z]{3}',
            Currency,
            ->(s) { Currency.new(s) },
            true,
            true
          )
        )

        assert_expression('I have a {currency} account and a {currency} account', ['currency', 'currency2'], 'I have a EUR account and a GBP account')
      end

      it 'exposes parameters in a generated expression' do
        expression = @generator.generate_expressions('I have 2 cukes and 1.5 euro')[0]
        types = expression.parameter_types.map(&:type)

        expect(types).to eq([Integer, Float])
      end

      it 'matches parameter types with optional capture groups' do
        @parameter_type_registry.define_parameter_type(
          ParameterType.new(
            'optional-flight',
            /(1st flight)?/,
            String,
            ->(s) { s },
            true,
            false
          )
        )
        @parameter_type_registry.define_parameter_type(
          ParameterType.new(
            'optional-hotel',
            /(1 hotel)?/,
            String,
            ->(s) { s },
            true,
            false
          )
        )

        expression = @generator.generate_expressions('I reach Stage 4: 1st flight -1 hotel')[0]
        # While you would expect this to be `I reach Stage {int}: {optional-flight} -{optional-hotel}`
        # the `-1` causes {int} to match just before {optional-hotel}.

        expect(expression.source).to eq('I reach Stage {int}: {optional-flight} {int} hotel')
      end

      it 'generates at most 256 expressions' do
        for i in 0..3
          @parameter_type_registry.define_parameter_type(
            ParameterType.new(
              "my-type-#{i}",
              /([a-z] )*?[a-z]/,
              String,
              ->(s) { s },
              true,
              false
            )
          )
        end
        # This would otherwise generate 4^11=4194300 expressions and consume just shy of 1.5GB.
        expressions = @generator.generate_expressions('a s i m p l e s t e p')

        expect(expressions.length).to eq(256)
      end

      it 'prefers expression with longest non empty match' do
        @parameter_type_registry.define_parameter_type(
          ParameterType.new(
            'zero-or-more',
            /[a-z]*/,
            String,
            ->(s) { s },
            true,
            false
          )
        )
        @parameter_type_registry.define_parameter_type(
          ParameterType.new(
            'exactly-one',
            /[a-z]/,
            String,
            ->(s) { s },
            true,
            false
          )
        )
        expressions = @generator.generate_expressions('a simple step')

        expect(expressions.length).to eq(2)
        expect(expressions[0].source).to eq('{exactly-one} {zero-or-more} {zero-or-more}')
        expect(expressions[1].source).to eq('{zero-or-more} {zero-or-more} {zero-or-more}')
      end

      context 'does not suggest parameter when match is' do
        before do
          @parameter_type_registry.define_parameter_type(
            ParameterType.new(
              'direction',
              /(up|down)/,
              String,
              ->(s) { s },
              true,
              false
            )
          )
        end

        it 'at the beginning of a word' do
          expect(@generator.generate_expressions('When I download a picture')[0].source).not_to eq('When I {direction}load a picture')
          expect(@generator.generate_expressions('When I download a picture')[0].source).to eq('When I download a picture')
        end

        it 'inside a word' do
          expect(@generator.generate_expressions('When I watch the muppet show')[0].source).not_to eq('When I watch the m{direction}pet show')
          expect(@generator.generate_expressions('When I watch the muppet show')[0].source).to eq('When I watch the muppet show')
        end

        it 'at the end of a word' do
          expect(@generator.generate_expressions('When I create a group')[0].source).not_to eq('When I create a gro{direction}')
          expect(@generator.generate_expressions('When I create a group')[0].source).to eq('When I create a group')
        end
      end

      context 'does suggest parameter when match is' do
        before do
          @parameter_type_registry.define_parameter_type(
            ParameterType.new(
              'direction',
              /(up|down)/,
              String,
              ->(s) { s },
              true,
              false
            )
          )
        end

        it 'a full word' do
          expect(@generator.generate_expressions('When I go down the road')[0].source).to eq('When I go {direction} the road')
          expect(@generator.generate_expressions('When I walk up the hill')[0].source).to eq('When I walk {direction} the hill')
          expect(@generator.generate_expressions('up the hill, the road goes down')[0].source).to eq('{direction} the hill, the road goes {direction}')
        end

        it 'wrapped around punctuation characters' do
          expect(@generator.generate_expressions('When direction is:down')[0].source).to eq('When direction is:{direction}')
          expect(@generator.generate_expressions('Then direction is down.')[0].source).to eq('Then direction is {direction}.')
        end
      end

      def assert_expression(expected_expression, expected_argument_names, text)
        generated_expression = @generator.generate_expressions(text)[0]
        expect(generated_expression.parameter_names).to eq(expected_argument_names)
        expect(generated_expression.source).to eq(expected_expression)

        cucumber_expression = CucumberExpression.new(generated_expression.source, @parameter_type_registry)
        match = cucumber_expression.match(text)

        raise "Expected text '#{text}' to match generated expression '#{generated_expression.source}'" if match.nil?

        expect(match.length).to eq(expected_argument_names.length)
      end
    end
  end
end
