# frozen_string_literal: true

# Copyright 2015-2017, the Linux Foundation, IDA, and the
# OpenSSF Best Practices badge contributors
# SPDX-License-Identifier: MIT

# rubocop: disable Metrics/ClassLength
class Badge
  ACCEPTABLE_PERCENTAGES = (0..99).to_a.freeze
  ACCEPTABLE_LEVELS = %w[passing silver gold].freeze

  # Make this a set so we can quickly determine if an input is "valid?"
  ACCEPTABLE_INPUTS = (
    ACCEPTABLE_PERCENTAGES + ACCEPTABLE_LEVELS
  ).to_set.freeze

  # These are copied from app/assets/images/badge_static_widths.txt
  # after running 'rake update_badge_images'.
  # We very rarely change the static images in a way that affects widths,
  # so it's simpler to just copy the information into the source code here.
  # As a style recommendation remove the comma from the last entry.
  # rubocop:disable Lint/SymbolConversion
  BADGE_WIDTHS = {
    'passing': 184,
    'silver': 172,
    'gold': 166,
    '0': 228,
    '1': 228,
    '2': 228,
    '3': 228,
    '4': 228,
    '5': 228,
    '6': 228,
    '7': 228,
    '8': 228,
    '9': 228,
    '10': 234,
    '11': 234,
    '12': 234,
    '13': 234,
    '14': 234,
    '15': 234,
    '16': 234,
    '17': 234,
    '18': 234,
    '19': 234,
    '20': 234,
    '21': 234,
    '22': 234,
    '23': 234,
    '24': 234,
    '25': 234,
    '26': 234,
    '27': 234,
    '28': 234,
    '29': 234,
    '30': 234,
    '31': 234,
    '32': 234,
    '33': 234,
    '34': 234,
    '35': 234,
    '36': 234,
    '37': 234,
    '38': 234,
    '39': 234,
    '40': 234,
    '41': 234,
    '42': 234,
    '43': 234,
    '44': 234,
    '45': 234,
    '46': 234,
    '47': 234,
    '48': 234,
    '49': 234,
    '50': 234,
    '51': 234,
    '52': 234,
    '53': 234,
    '54': 234,
    '55': 234,
    '56': 234,
    '57': 234,
    '58': 234,
    '59': 234,
    '60': 234,
    '61': 234,
    '62': 234,
    '63': 234,
    '64': 234,
    '65': 234,
    '66': 234,
    '67': 234,
    '68': 234,
    '69': 234,
    '70': 234,
    '71': 234,
    '72': 234,
    '73': 234,
    '74': 234,
    '75': 234,
    '76': 234,
    '77': 234,
    '78': 234,
    '79': 234,
    '80': 234,
    '81': 234,
    '82': 234,
    '83': 234,
    '84': 234,
    '85': 234,
    '86': 234,
    '87': 234,
    '88': 234,
    '89': 234,
    '90': 234,
    '91': 234,
    '92': 234,
    '93': 234,
    '94': 234,
    '95': 234,
    '96': 234,
    '97': 234,
    '98': 234,
    '99': 234
  }.freeze
  # rubocop:enable Lint/SymbolConversion

  attr_accessor :svg

  class << self
    # Class methods
    include Enumerable

    # Creates and caches Badge instances for the given level.
    # @param level [String, Integer] the badge level (percentage 0-99 or 'passing', 'silver', 'gold')
    # @return [Badge] the badge instance for the specified level
    # @raise [ArgumentError] if level is not valid
    def [](level)
      raise ArgumentError unless valid?(level)

      @badges ||= {}
      @badges[level] ||= new(level)
    end

    # Returns all badge instances for all acceptable levels.
    # Creates badges if they don't exist yet.
    # @return [Array<Badge>] array of all badge instances
    def all
      create_all unless @badges&.length == 103
      ACCEPTABLE_INPUTS.map { |level| self[level] }
    end

    # Creates badge instances for all acceptable levels.
    # Initializes the internal badges cache.
    # @return [void]
    def create_all
      @badges = {}
      ACCEPTABLE_INPUTS.each { |level| @badges[level] = new(level) }
    end

    # Iterates over all badge instances.
    # Implements Enumerable interface.
    # @yield [Badge] each badge instance
    # @return [Badge] self for method chaining
    def each
      all.each { |badge| yield badge }
      self
    end

    # Checks if the given level is valid for badge creation.
    # @param level [String, Integer] the level to validate
    # @return [Boolean] true if level is acceptable
    def valid?(level)
      ACCEPTABLE_INPUTS.include?(level)
    end
  end

  # Creates a new Badge instance for the specified level.
  # Loads the corresponding SVG content from static files.
  # @param level [String, Integer] the badge level
  # @raise [ArgumentError] if level is not valid
  def initialize(level)
    raise ArgumentError unless self.class.valid?(level)

    @svg = load_svg(level)
  end

  # Returns the SVG content as a string.
  # @return [String] the badge SVG content
  def to_s
    svg
  end

  private

  # Loads SVG content from the corresponding static file.
  # @param level [String, Integer] the badge level
  # @return [String] the SVG file content or empty string if invalid
  def load_svg(level)
    # Defensive programming: only allow valid levels.
    # This was checked earlier, but we re-check here so we're sure *and*
    # that static analysis tools will know we've checked it.
    return '' unless self.class.valid?(level)

    File.read("app/assets/images/badge_static_#{level}.svg")
  end
end
# rubocop: enable Metrics/ClassLength
