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

module Contrast
  module Agent
    module Assess
      module Policy
        module Propagator
          # Propagation that results in all the tags of the source being
          # applied to the totality of the target. The target's preexisting
          # tags are unaffected beyond any merging of overlapping tags.
          class Splat < Contrast::Agent::Assess::Policy::Propagator::Base
            class << self
              def propagate propagation_node, preshift, target
                tracked_inputs = []

                propagation_node.sources.each do |source|
                  case source
                  when Contrast::Utils::ObjectShare::OBJECT_KEY
                    tracked_inputs << preshift.object if Contrast::Agent::Assess::Tracker.tracked?(preshift.object)
                  else
                    find_argument_inputs(tracked_inputs, preshift.args[source])
                  end
                end

                splat_tags(tracked_inputs, target)
                Contrast::Agent::Assess::Tracker.properties(target)&.cleanup_tags
              end

              def splat_tags tracked_inputs, target
                return if tracked_inputs.empty?
                return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))

                tracked_inputs.each do |input|
                  input_properties = Contrast::Agent::Assess::Tracker.properties(input)
                  next unless input_properties

                  properties.splat_from(input, target)
                end
              end

              private

              # The arguments to the splat method are complex and of multiple types. As such, we need to handle
              # Strings and iterables to determine the tracked inputs on which to act.
              #
              # @param tracked_inputs [Array] storage for the inputs to act on later
              # @param arg [Object] an input to the method which act as sources for this propagation.
              def find_argument_inputs tracked_inputs, arg
                if arg.is_a?(String)
                  tracked_inputs << arg if Contrast::Agent::Assess::Tracker.tracked?(arg)
                elsif Contrast::Utils::DuckUtils.iterable_hash?(arg)
                  arg.each_pair do |key, value|
                    tracked_inputs << key if tracked_value?(key)
                    tracked_inputs << value if tracked_value?(value)
                  end
                elsif Contrast::Utils::DuckUtils.iterable_enumerable?(arg)
                  arg.each do |value|
                    tracked_inputs << value if tracked_value?(value)
                  end
                end
              end
            end
          end
        end
      end
    end
  end
end