module Ameba::Formatter
  module Util
    def deansify(message : String?) : String?
      message.try &.gsub(/\x1b[^m]*m/, "").presence
    end

    def trim(str, max_length = 120, ellipsis = " ...")
      if (str.size - ellipsis.size) > max_length
        str = str[0, max_length]
        if str.size > ellipsis.size
          str = str[0...-ellipsis.size] + ellipsis
        end
      end
      str
    end

    def context(lines, lineno, context_lines = 3, remove_empty = true)
      pre_context, post_context = %w[], %w[]

      lines.each_with_index do |line, i|
        case i + 1
        when lineno - context_lines...lineno
          pre_context << line
        when lineno + 1..lineno + context_lines
          post_context << line
        end
      end

      if remove_empty
        # remove empty lines at the beginning ...
        while pre_context.first?.try(&.blank?)
          pre_context.shift
        end
        # ... and the end
        while post_context.last?.try(&.blank?)
          post_context.pop
        end
      end

      {pre_context, post_context}
    end

    def affected_code(issue : Issue, context_lines = 0, max_length = 120, ellipsis = " ...", prompt = "> ")
      return unless location = issue.location

      affected_code(issue.code, location, issue.end_location, context_lines, max_length, ellipsis, prompt)
    end

    def affected_code(code, location, end_location = nil, context_lines = 0, max_length = 120, ellipsis = " ...", prompt = "> ")
      lines = code.split('\n') # must preserve trailing newline
      lineno, column =
        location.line_number, location.column_number

      return unless affected_line = lines[lineno - 1]?.presence

      if column < max_length
        affected_line = trim(affected_line, max_length, ellipsis)
      end

      show_context = context_lines > 0

      if show_context
        pre_context, post_context =
          context(lines, lineno, context_lines)

        position = prompt.size + column
        position -= 1
      else
        affected_line_size, affected_line =
          affected_line.size, affected_line.lstrip

        position = column - (affected_line_size - affected_line.size) + prompt.size
        position -= 1
      end

      String.build do |str|
        if show_context
          pre_context.try &.each do |line|
            line = trim(line, max_length, ellipsis)
            str << prompt
            str.puts(line.colorize(:dark_gray))
          end
        end

        str << prompt
        str.puts(affected_line.colorize(:white))

        str << (" " * position)
        str << "^".colorize(:yellow)

        if end_location
          end_lineno = end_location.line_number
          end_column = end_location.column_number

          if end_lineno == lineno && end_column > column
            end_position = end_column - column
            end_position -= 1

            str << ("-" * end_position).colorize(:dark_gray)
            str << "^".colorize(:yellow)
          end
        end

        str.puts

        if show_context
          post_context.try &.each do |line|
            line = trim(line, max_length, ellipsis)
            str << prompt
            str.puts(line.colorize(:dark_gray))
          end
        end
      end
    end
  end
end
