# frozen_string_literal: true

# Patches
class String
  def indent(depth = 1, size = 4)
    split("\n", -1).collect { |line| (' ' * size * depth) + line }.join("\n")
  end

  # Returns a reformatted string in camelCase.
  # @return [String]
  def camel_case
    tokens = tokens_for_case_helper.collect { |token| Litany::ABBREVIATIONS.include?(token.downcase) ? token.upcase : token.capitalize }
    tokens[0].downcase!
    tokens.join
  end

  # Returns a reformatted string in dash-case. Note: This is not a full slugify function.
  # @return [String]
  def dash_case
    tokens_for_case_helper.collect(&:downcase).join('-')
  end

  # Returns a reformatted string in PascalCase.
  # @return [String]
  def pascal_case
    tokens_for_case_helper.collect { |token| Litany::ABBREVIATIONS.include?(token.downcase) ? token.upcase : token.capitalize }.join
  end

  # Returns a reformatted string in snake_case.
  # @return [String]
  def snake_case
    tokens_for_case_helper.collect(&:downcase).join('_')
  end

  # Splits the string into a array of tokens that should be considered together.
  # @return [Array<String>]
  private def tokens_for_case_helper
    # This function is over documented because regexes are confusing and at least one in use isn't simple.

    # First split the string into chunks.  This will consume all non-alphanumeric characters from the string.
    split(/[^a-zA-Z0-9]/).collect do |part|
      # For each chunk we use look ahead and look behind assertions to split the chunk into tokens.
      #   There are actually four different regexes in the below call, but done as one single regex for cleaner code and hopefully some performance gains.
      #   None of the regexes consume any characters from the chunks.
      #
      #   1) Split if the next two characters are capital followed by a lower.
      #   2) Split if the last character is a lower and the next is a capital.
      #
      #   The next three rules happen as part of a case insensitive inner split:
      #
      #   3) Split if the last phrase is a known abbreviation and the next character is a number.
      #   4) Split if the last character is a number and the next phrase is a known abbreviation.
      #   5) Split if the last phrase is a known abbreviation, and the previous phrase is a known abbreviation.
      abbreviations = Litany::ABBREVIATIONS.join('|')
      part.split(/(?:(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z]))/).collect { |sub_part| sub_part.split(/(?i)(?:(?<=#{abbreviations})(?=[0-9])|(?<=[0-9])(?=#{abbreviations})|(?<=#{abbreviations})(?=#{abbreviations}))/) }
    end.flatten
  end
end
