# frozen_string_literal: true

module TTY
  # A class responsible for shell prompt interactions.
  class Prompt
    # A class responsible for gathering numeric input from range
    #
    # @api public
    class Slider
      HELP = '(Use arrow keys, press Enter to select)'.freeze

      FORMAT = ':slider %d'.freeze

      # Initailize a Slider
      #
      # @param [Prompt] prompt
      #   the prompt
      # @param [Hash] options
      #   the options to configure this slider
      # @option options [Integer] :min The minimum value
      # @option options [Integer] :max The maximum value
      # @option options [Integer] :step The step value
      # @option options [String] :format The display format
      #
      # @api public
      def initialize(prompt, **options)
        @prompt       = prompt
        @prefix       = options.fetch(:prefix) { @prompt.prefix }
        @min          = options.fetch(:min) { 0 }
        @max          = options.fetch(:max) { 10 }
        @step         = options.fetch(:step) { 1 }
        @default      = options[:default]
        @active_color = options.fetch(:active_color) { @prompt.active_color }
        @help_color   = options.fetch(:help_color) { @prompt.help_color }
        @format       = options.fetch(:format) { FORMAT }
        @symbols      = @prompt.symbols.merge(options.fetch(:symbols, {}))
        @first_render = true
        @done         = false
      end

      # Change symbols used by this prompt
      #
      # @param [Hash] new_symbols
      #   the new symbols to use
      #
      # @api public
      def symbols(new_symbols = (not_set = true))
        return @symbols if not_set
        @symbols.merge!(new_symbols)
      end

      # Setup initial active position
      #
      # @return [Integer]
      #
      # @api private
      def initial
        if @default.nil?
          range.size / 2
        else
          range.index(@default)
        end
      end

      # Range of numbers to render
      #
      # @return [Array[Integer]]
      #
      # @apip private
      def range
        (@min..@max).step(@step).to_a
      end

      # @api public
      def default(value)
        @default = value
      end

      # @api public
      def min(value)
        @min = value
      end

      # @api public
      def max(value)
        @max = value
      end

      # @api public
      def step(value)
        @step = value
      end

      def format(value)
        @format = value
      end

      # Call the slider by passing question
      #
      # @param [String] question
      #   the question to ask
      #
      # @apu public
      def call(question, &block)
        @question = question
        block.call(self) if block
        @active = initial
        @prompt.subscribe(self) do
          render
        end
      end

      def keyleft(*)
        @active -= 1 if @active > 0
      end
      alias keydown keyleft

      def keyright(*)
        @active += 1 if (@active + 1) < range.size
      end
      alias keyup keyright

      def keyreturn(*)
        @done = true
      end
      alias keyspace keyreturn
      alias keyenter keyreturn

      private

      # Render an interactive range slider.
      #
      # @api private
      def render
        @prompt.print(@prompt.hide)
        until @done
          question = render_question
          @prompt.print(question)
          @prompt.read_keypress
          refresh(question.lines.count)
        end
        @prompt.print(render_question)
        answer
      ensure
        @prompt.print(@prompt.show)
      end

      # Clear screen
      #
      # @param [Integer] lines
      #   the lines to clear
      #
      # @api private
      def refresh(lines)
        @prompt.print(@prompt.clear_lines(lines))
      end

      # @return [Integer]
      #
      # @api private
      def answer
        range[@active]
      end

      # Render question with the slider
      #
      # @return [String]
      #
      # @api private
      def render_question
        header = ["#{@prefix}#{@question} "]
        if @done
          header << @prompt.decorate(answer.to_s, @active_color)
          header << "\n"
        else
          header << render_slider
        end
        if @first_render
          header << "\n" + @prompt.decorate(HELP, @help_color)
          @first_render = false
        end
        header.join
      end

      # Render slider representation
      #
      # @return [String]
      #
      # @api private
      def render_slider
        slider = (@symbols[:line] * @active) +
                 @prompt.decorate(@symbols[:bullet], @active_color) +
                 (@symbols[:line] * (range.size - @active - 1))
        value = " #{range[@active]}"
        @format.gsub(':slider', slider) % [value]
      end
    end # Slider
  end # Prompt
end # TTY
