# Copyright (c) 2021 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 target exactly as is. The target's preexisting tags
          # are unaffected beyond any merging of overlapping tags.
          class MatchData < Contrast::Agent::Assess::Policy::Propagator::Base
            class << self
              def square_bracket_tagger propagation_node, preshift, ret, _block
                case ret
                when Array
                  ret.each_with_index do |return_value, index|
                    next unless return_value

                    target_matchdata_index = if preshift.args[0].is_a?(Range)
                                               arg_range = preshift.args[0]
                                               arg_range.to_a.empty? ? index + 1 : arg_range.to_a[index]
                                             else
                                               preshift.args[index]
                                             end
                    square_bracket_single(target_matchdata_index, preshift, return_value, propagation_node)
                  end
                when String
                  target_matchdata_index = preshift.args[0]
                  square_bracket_single(target_matchdata_index, preshift, ret, propagation_node)
                end

                ret
              end

              def captures_tagger propagation_node, preshift, ret, _block
                ret.each_with_index do |return_value, index|
                  next unless return_value

                  targetted_index = index + 1
                  square_bracket_single(targetted_index, preshift, return_value, propagation_node)
                end
                ret
              end

              def to_a_tagger propagation_node, preshift, ret, _block
                ret.each_with_index do |return_value, index|
                  next unless return_value

                  square_bracket_single(index, preshift, return_value, propagation_node)
                end
                ret
              end

              def values_at_tagger propagation_node, preshift, ret, _block
                ret.each_with_index do |return_value, return_index|
                  next unless return_value

                  original_group_arg_index = preshift.args[return_index]
                  square_bracket_single(original_group_arg_index, preshift, return_value, propagation_node)
                end
                ret
              end

              private

              def square_bracket_single argument_index, preshift, return_value, propagation_node
                original_start_index = preshift.object.begin(argument_index)
                original_end_index = preshift.object.end(argument_index)
                return unless (original_properties = Contrast::Agent::Assess::Tracker.properties(preshift.object))

                applicable_tags = original_properties.tags_at_range(original_start_index...original_end_index)
                return if applicable_tags.empty?
                return unless (return_properties = Contrast::Agent::Assess::Tracker.properties!(return_value))

                applicable_tags.each do |tag_name, tag_ranges|
                  return_properties.set_tags(tag_name, tag_ranges)
                end
                return_properties.build_event(propagation_node, return_value, preshift.object, return_value, preshift.args)
              end
            end
          end
        end
      end
    end
  end
end