# Copyright (c) 2023 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
              # This patch method is used to track through the MatchData#[] and
              # MatchData#match methods, since the last was introduced after
              # Ruby 3.1.0, but shares similar functionality, except it does not
              # support ranges.
              #
              # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
              # propagation action required by this method.
              # @param preshift [Object] pre call state of the things.
              # @param ret [Object] the return value of the method.
              # @param _block [Proc] the block passed to the method.
              # @return [Object] the return value of the method.
              def square_bracket_tagger propagation_node, preshift, ret, _block
                case ret
                when Array
                  idx = 0
                  while idx < ret.length
                    return_value = ret[idx]
                    index = idx
                    idx += 1
                    next unless return_value

                    square_bracket_single(target_matchdata_index(preshift, 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

              # Captures is a method that returns an array of MatchData objects.
              #
              # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
              # propagation action required by this method.
              # @param preshift [Object] pre call state of the things.
              # @param ret [Object] the return value of the method.
              # @param _block [Proc] the block passed to the method.
              # @return [Object] the return value of the method.
              def captures_tagger propagation_node, preshift, ret, _block
                return unless ret

                idx = 0
                while idx < ret.length
                  return_value = ret[idx]
                  index = idx
                  idx += 1
                  next unless return_value

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

              # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
              # propagation action required by this method.
              # @param preshift [Object] pre call state of the things.
              # @param ret [Object] the return value of the method.
              # @param _block [Proc] the block passed to the method.
              # @return [Object] the return value of the method.
              def to_a_tagger propagation_node, preshift, ret, _block
                idx = 0
                while idx < ret.length
                  return_value = ret[idx]
                  index = idx
                  idx += 1
                  next unless return_value

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

              # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
              # propagation action required by this method.
              # @param preshift [Object] pre call state of the things.
              # @param ret [Object] the return value of the method.
              # @param _block [Proc] the block passed to the method.
              # @return [Object] the return value of the method.
              def values_at_tagger propagation_node, preshift, ret, _block
                idx = 0
                while idx < ret.length
                  return_value = ret[idx]
                  return_index = idx
                  idx += 1
                  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

              # @param preshift [Object] pre call state of the things.
              # @param index [Integer] the index of the argument to retrieve.
              # @return [Integer] the index of the argument to retrieve.
              def target_matchdata_index preshift, 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
              end

              # Handles the propagation of a single argument.
              #
              # @param argument_index [Integer] the index of the argument to retrieve.
              # @param preshift [Object] pre call state of the things.
              # @param return_value [Object] the return value of the method.
              # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
              # propagation action required by this method.
              # @return [Object] the return value of the method.
              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
                event_data = Contrast::Agent::Assess::Events::EventData.new(propagation_node,
                                                                            return_value,
                                                                            preshift.object,
                                                                            return_value,
                                                                            preshift.args)
                return_properties.build_event(event_data)
              end
            end
          end
        end
      end
    end
  end
end