# frozen_string_literal: true

class Hash

  # rubocop:disable Style/CaseEquality
  def deep_dup
    hash = dup

    each_pair do |key, value|
      if key.frozen? && ::String === key
        hash[key] = value.deep_dup
      else
        hash.delete(key)
        hash[key.deep_dup] = value.deep_dup
      end
    end

    hash
  end
  # rubocop:enable Style/CaseEquality

  def deep_merge(other_hash, &block)
    dup.deep_merge!(other_hash, &block)
  end

  def deep_merge!(other_hash, &block)
    merge!(other_hash) do |key, this_val, other_val|
      if this_val.is_a?(Hash) && other_val.is_a?(Hash)
        this_val.deep_merge(other_val, &block)
      elsif defined?(yield)
        yield(key, this_val, other_val)
      else
        other_val
      end
    end
  end

  def except(*keys)
    slice(*self.keys - keys)
  end

  def except!(*keys)
    keys.each { |key| delete(key) }
    self
  end

  def extract!(*keys)
    keys.each_with_object({}) { |key, hash| hash[key] = delete(key) if key?(key) }
  end

  def reverse_merge(other_hash)
    other_hash.merge(self)
  end

  def reverse_merge!(other_hash)
    other_hash.merge!(self)
  end

  def stringify_keys
    transform_keys(&:to_s)
  end

  def stringify_keys!
    transform_keys!(&:to_s)
  end

  def symbolize_keys
    transform_keys do |key|
      key.to_sym
    rescue StandardError
      key
    end
  end

  def symbolize_keys!
    transform_keys! do |key|
      key.to_sym
    rescue StandardError
      key
    end
  end

end