# frozen_string_literal: true

require 'cucumber/cucumber_expressions/parameter_type_matcher'
require 'cucumber/cucumber_expressions/generated_expression'
require 'cucumber/cucumber_expressions/combinatorial_generated_expression_factory'

module Cucumber
  module CucumberExpressions
    class CucumberExpressionGenerator
      def initialize(parameter_type_registry)
        @parameter_type_registry = parameter_type_registry
      end

      def generate_expressions(text)
        parameter_type_combinations = []
        parameter_type_matchers = create_parameter_type_matchers(text)
        expression_template = +''
        pos = 0

        loop do
          matching_parameter_type_matchers = []
          parameter_type_matchers.each do |parameter_type_matcher|
            advanced_parameter_type_matcher = parameter_type_matcher.advance_to(pos)
            matching_parameter_type_matchers.push(advanced_parameter_type_matcher) if advanced_parameter_type_matcher.find
          end

          if matching_parameter_type_matchers.any?
            matching_parameter_type_matchers = matching_parameter_type_matchers.sort
            best_parameter_type_matcher = matching_parameter_type_matchers[0]
            best_parameter_type_matchers = matching_parameter_type_matchers.select do |m|
              (m <=> best_parameter_type_matcher).zero?
            end

            # Build a list of parameter types without duplicates. The reason there
            # might be duplicates is that some parameter types have more than one regexp,
            # which means multiple ParameterTypeMatcher objects will have a reference to the
            # same ParameterType.
            # We're sorting the list so prefer_for_regexp_match parameter types are listed first.
            # Users are most likely to want these, so they should be listed at the top.
            parameter_types = []
            best_parameter_type_matchers.each do |parameter_type_matcher|
              parameter_types.push(parameter_type_matcher.parameter_type) unless parameter_types.include?(parameter_type_matcher.parameter_type)
            end
            parameter_types.sort!

            parameter_type_combinations.push(parameter_types)

            expression_template += escape(text.slice(pos...best_parameter_type_matcher.start))
            expression_template += '{%s}'

            pos = best_parameter_type_matcher.start + best_parameter_type_matcher.group.length
          else
            break
          end

          break if pos >= text.length
        end

        expression_template += escape(text.slice(pos..-1))

        CombinatorialGeneratedExpressionFactory.new(
          expression_template,
          parameter_type_combinations
        ).generate_expressions
      end

      private

      def create_parameter_type_matchers(text)
        parameter_matchers = []
        @parameter_type_registry.parameter_types.each do |parameter_type|
          parameter_matchers += create_parameter_type_matchers2(parameter_type, text) if parameter_type.use_for_snippets
        end
        parameter_matchers
      end

      def create_parameter_type_matchers2(parameter_type, text)
        regexps = parameter_type.regexps
        regexps.map do |regexp|
          regexp = Regexp.new("(#{regexp})")
          ParameterTypeMatcher.new(parameter_type, regexp, text, 0)
        end
      end

      def escape(s)
        s.gsub('%', '%%')
         .gsub('(', '\\(')
         .gsub(/{/, '\\{')
         .gsub('/', '\\/')
      end
    end
  end
end
