<commit_before># frozen_string_literal: true

require_relative "list"
require_relative "selected_choices"

module TTY
  class Prompt
    # A class responsible for rendering multi select list menu.
    # Used by {Prompt} to display interactive choice menu.
    #
    # @api private
    class MultiList < List
      # Create instance of TTY::Prompt::MultiList menu.
      #
      # @param [Prompt] :prompt
      # @param [Hash] options
      #
      # @api public
      def initialize(prompt, **options)
        super
        @selected = SelectedChoices.new
        @help = options[:help]
        @echo = options.fetch(:echo, true)
        @min  = options[:min]
        @max  = options[:max]
      end

      # Set a minimum number of choices
      #
      # @api public
      def min(value)
        @min = value
      end

      # Set a maximum number of choices
      #
      # @api public
      def max(value)
        @max = value
      end

      # Callback fired when enter/return key is pressed
      #
      # @api private
      def keyenter(*)
        valid = true
        valid = @min <= @selected.size if @min
        valid = @selected.size <= @max if @max

        super if valid
      end
      alias keyreturn keyenter

      # Callback fired when space key is pressed
      #
      # @api private
      def keyspace(*)
        active_choice = choices[@active - 1]
        if @selected.include?(active_choice)
          @selected.delete_at(@active - 1)
        else
          return if @max && @selected.size >= @max
          @selected.insert(@active - 1, active_choice)
        end
      end

      # Selects all choices when Ctrl+A is pressed
      #
      # @api private
      def keyctrl_a(*)
        return if @max && @max < choices.size
        @selected = SelectedChoices.new(choices.enabled, choices.enabled_indexes)
      end

      # Revert currently selected choices when Ctrl+I is pressed
      #
      # @api private
      def keyctrl_r(*)
        return if @max && @max < choices.size
        indexes = choices.each_with_index.reduce([]) do |acc, (choice, idx)|
                    acc << idx if !choice.disabled? && !@selected.include?(choice)
                    acc
                  end
        @selected = SelectedChoices.new(choices.enabled - @selected.to_a, indexes)
      end

      private

      # Setup default options and active selection
      #
      # @api private
      def setup_defaults
        validate_defaults
        # At this stage, @choices matches all the visible choices.
        default_indexes = @default.map { |d| d - 1 }
        @selected = SelectedChoices.new(@choices.values_at(*default_indexes),
                                        default_indexes)

        if !@default.empty?
          @active = @default.last
        else
          @active = @choices.index { |choice| !choice.disabled? } + 1
        end
      end

      # Generate selected items names
      #
      # @return [String]
      #
      # @api private
      def selected_names
        @selected.map(&:name).join(", ")
      end

      # Header part showing the minimum/maximum number of choices
      #
      # @return [String]
      #
      # @api private
      def minmax_help
        help = []
        help << "min. #{@min}" if @min
        help << "max. #{@max}" if @max
        "(%s) " % [help.join(" ")]
      end

      # Build a default help text
      #
      # @return [String]
      #
      # @api private
      def default_help<commit_msg>Change to separate min and max values<commit_after># frozen_string_literal: true

require_relative "list"
require_relative "selected_choices"

module TTY
  class Prompt
    # A class responsible for rendering multi select list menu.
    # Used by {Prompt} to display interactive choice menu.
    #
    # @api private
    class MultiList < List
      # Create instance of TTY::Prompt::MultiList menu.
      #
      # @param [Prompt] :prompt
      # @param [Hash] options
      #
      # @api public
      def initialize(prompt, **options)
        super
        @selected = SelectedChoices.new
        @help = options[:help]
        @echo = options.fetch(:echo, true)
        @min  = options[:min]
        @max  = options[:max]
      end

      # Set a minimum number of choices
      #
      # @api public
      def min(value)
        @min = value
      end

      # Set a maximum number of choices
      #
      # @api public
      def max(value)
        @max = value
      end

      # Callback fired when enter/return key is pressed
      #
      # @api private
      def keyenter(*)
        valid = true
        valid = @min <= @selected.size if @min
        valid = @selected.size <= @max if @max

        super if valid
      end
      alias keyreturn keyenter

      # Callback fired when space key is pressed
      #
      # @api private
      def keyspace(*)
        active_choice = choices[@active - 1]
        if @selected.include?(active_choice)
          @selected.delete_at(@active - 1)
        else
          return if @max && @selected.size >= @max
          @selected.insert(@active - 1, active_choice)
        end
      end

      # Selects all choices when Ctrl+A is pressed
      #
      # @api private
      def keyctrl_a(*)
        return if @max && @max < choices.size
        @selected = SelectedChoices.new(choices.enabled, choices.enabled_indexes)
      end

      # Revert currently selected choices when Ctrl+I is pressed
      #
      # @api private
      def keyctrl_r(*)
        return if @max && @max < choices.size
        indexes = choices.each_with_index.reduce([]) do |acc, (choice, idx)|
                    acc << idx if !choice.disabled? && !@selected.include?(choice)
                    acc
                  end
        @selected = SelectedChoices.new(choices.enabled - @selected.to_a, indexes)
      end

      private

      # Setup default options and active selection
      #
      # @api private
      def setup_defaults
        validate_defaults
        # At this stage, @choices matches all the visible choices.
        default_indexes = @default.map { |d| d - 1 }
        @selected = SelectedChoices.new(@choices.values_at(*default_indexes),
                                        default_indexes)

        if !@default.empty?
          @active = @default.last
        else
          @active = @choices.index { |choice| !choice.disabled? } + 1
        end
      end

      # Generate selected items names
      #
      # @return [String]
      #
      # @api private
      def selected_names
        @selected.map(&:name).join(", ")
      end

      # Header part showing the minimum/maximum number of choices
      #
      # @return [String]
      #
      # @api private
      def minmax_help
        help = []
        help << "min. #{@min}" if @min
        help << "max. #{@max}" if @max
        "(%s) " % [help.join(", ")]
      end

      # Build a default help text
      #
      # @return [String]
      #
      # @api private
      def default_help
{'fraction_non_alphanumeric': 0.09749463135289907, 'fraction_numerical': 0.0014316392269148174, 'mean_word_length': 1.7406826206355432, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4123951', 'n_tokens_mistral': 2005, 'n_tokens_neox': 1943, 'n_words': 593}