# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

module Contrast
  module Utils
    # Utilities for encoding and normalizing strings
    module StringUtils
      class << self
        UTF8 = 'utf-8'
        HTTP_PREFIX = 'HTTP_'

        # Convenience method. We assume that we're working on Strings or tags
        # String representations of things. To that end, we'll to_s anything
        # that comes in before returning its length.
        #
        # But don't worry though, String.to_s just returns self. teehee
        def ret_length string
          string.nil? ? 0 : string.to_s.length
        end

        def present? str
          !str.nil? && !str.to_s.empty?
        end

        # Protobuf has a very strict typing. Nil is not a String and will throw
        # an exception if you try to set it. Use this to be safe.
        # Uses the object share to avoid creating several new strings per request
        def protobuf_safe_string string
          string.nil? ? Contrast::Utils::ObjectShare::EMPTY_STRING : string.to_s
        end

        # Truncate a string to 255 characters max length
        #
        # @param str [String] the string tt truncate
        # @param default [String] what to default to
        # @return [String]
        def truncate str, default = Contrast::Utils::ObjectShare::EMPTY_STRING
          return default if str.nil?

          str.to_s[0..255]
        end

        # Cast the given object, which should be a String, into a UTF-8 String for reporting. All given objects will be
        # cast to their to_s form, except nil which will become the ObjectShare::EMPTY_STRING, and then cast.
        #
        # @param str [String, Object, nil]
        # @return [String]
        def force_utf8 str
          return Contrast::Utils::ObjectShare::EMPTY_STRING unless str

          str = str.to_s
          if str.encoding == Encoding::UTF_8
            str = str.encode(UTF8, invalid: :replace, undef: :replace) unless str.valid_encoding?
          else
            str = str.encode(UTF8, str.encoding, invalid: :replace, undef: :replace)
          end
          str.to_s
        rescue StandardError => e
          # We were unable to switch the String to a UTF-8 format.
          # Return non-nil so as not to throw an exception later when trying
          # to do regexp or other compares on the String
          Contrast::CONFIG.proto_logger.trace('Unable to cast String to UTF-8 format', e, value: str)

          Contrast::Utils::ObjectShare::EMPTY_STRING
        end

        # Given a string return a normalized version of that string.
        # Keys are memoized so that the normalization process doesn't need
        # to happen every time.
        #
        # @param str [String] the String to normalize
        # @return [String] a copy of the given String, upper cased, trimmed,
        #   dashes replaced with underscore, and HTTP trimmed
        def normalized_key str
          return unless str

          str = str.to_s
          @_normalized_keys ||= {}
          if @_normalized_keys.key?(str)
            @_normalized_keys[str]
          else
            upped = str.upcase
            stripped = upped.strip! || upped
            trimmed = stripped.tr!('-', '_') || stripped
            cut = trimmed.start_with?(HTTP_PREFIX) ? trimmed[5..] : trimmed
            @_normalized_keys[str] = cut
          end
        end

        # transform string from snake_case to Capitalized Text
        #
        # @param str[String] string to transform
        # @return [String]
        def transform_string str
          return unless str

          str.split('-').map(&:capitalize).join(' ')
        end
      end
    end
  end
end