# frozen_string_literal: true

require "reline"

module Prompts
  class Prompt
    def self.ask(label: nil, prompt: "> ", hint: nil, default: nil, required: false, validate: nil)
      instance = new(label: label, prompt: prompt, hint: hint, default: default, required: required, validate: validate)
      yield instance if block_given?
      instance.ask
    end

    def initialize(label: nil, prompt: "> ", hint: nil, default: nil, required: false, validate: nil)
      @label = label
      @prompt = prompt
      @hint = hint
      @default = default
      @required = required
      @validate = validate

      @content = nil
      @error = nil
      @attempts = 0
      @instructions = nil
      @validations = []
      @choice = nil
      @content_prepared = false
    end

    def content(&block)
      @content ||= Prompts::Content.new
      yield @content
      @content
    end

    # standard:disable Style/TrivialAccessors
    def label(label)
      @label = label
    end

    def hint(hint)
      @hint = hint
    end

    def default(default)
      @default = default
    end
    # standard:enable Style/TrivialAccessors

    def ask
      prepare_content if !@content_prepared
      prepare_default if @default
      prepare_validations

      loop do
        @content.render
        *initial_prompt_lines, last_prompt_line = formatted_prompt
        puts initial_prompt_lines.join("\n") if initial_prompt_lines.any?
        response = Reline.readline(last_prompt_line, _history = false).chomp
        @choice = resolve_choice_from(response)

        if (@error = ensure_validity(response))
          @content.reset!
          @content.paragraph Fmt("%{error}|>red|>bold", error: @error + " Try again (×#{@attempts})...")
          @attempts += 1
          next
        else
          break @choice
        end
      end

      @choice
    rescue Interrupt
      exit 0
    end

    def prepend_content(*lines)
      @content.prepend(*lines)
    end

    def prepare_content
      @content ||= Prompts::Content.new
      @content.paragraph formatted_label if @label
      @content.paragraph formatted_hint if @hint
      @content.paragraph formatted_error if @error
      @content_prepared = true
      @content
    end

    private

    def prepare_default
      Reline.pre_input_hook = -> do
        Reline.insert_text @default.to_s
        # Remove the hook right away.
        Reline.pre_input_hook = nil
      end
    end

    def prepare_validations
      if @required
        error_message = @required.is_a?(String) ? @required : "Value cannot be empty."
        @validations << ->(input) { error_message if input.empty? }
      end

      if @validate
        @validations << @validate
      end
    end

    def resolve_choice_from(response)
      response
    end

    def formatted_prompt
      prompt_with_space = @prompt.end_with?(SPACE) ? @prompt : @prompt + SPACE
      ansi_prompt = Fmt("%{prompt}|>faint|>bold", prompt: prompt_with_space)
      @formatted_prompt ||= Paragraph.new(ansi_prompt, width: MAX_WIDTH).lines
    end

    def formatted_label
      Fmt("%{label}|>cyan|>bold %{instructions}|>faint|>italic", label: @label, instructions: @instructions ? "(#{@instructions})" : "")
    end

    def formatted_hint
      Fmt("%{hint}|>faint|>bold", hint: @hint)
    end

    def formatted_error
      Fmt("%{error}|>red|>bold", error: @error + " Try again (×#{@attempts})...")
    end

    def ensure_validity(response)
      @validations.each do |validation|
        result = validation.call(response)
        return result if result
      end
      nil
    end
  end
end
