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

require 'contrast/agent/patching/policy/patch'
require 'contrast/agent/patching/policy/patcher'
require 'contrast/components/interface'

module Contrast
  module Extension
    module Assess
      # This is our patch of the Array class required to handle propagation
      # Disclaimer: there may be a better way, but we're in a 'get it work' state.
      # Hopefully, we'll be in a 'get it right' state soon.
      class ArrayPropagator
        include Contrast::Components::Interface

        access_component :scope

        ARRAY_JOIN_HASH = {
            'class_name' => 'Array',
            'instance_method' => true,
            'method_visibility' => 'public',
            'method_name' => 'join',
            'action' => 'CUSTOM',
            'source' => 'O',
            'target' => 'R',
            'patch_class' => 'NOOP',
            'patch_method' => 'cs__track_join'
        }.cs__freeze
        ARRAY_JOIN_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(ARRAY_JOIN_HASH)

        class << self
          # When you call join, they use an internal thing, so there's no good way to get at the thing being returned.
          # Multiple Strings are appended with the #join method. Because that
          # operation happens in C, we have to do it here rather than rely on the
          # patch of our String append or concat methods.
          def cs__track_join ary, separator, ret
            return ret unless ary&.any? { |element| Contrast::Agent::Assess::Tracker.tracked?(element) }
            return ret if Contrast::Agent::Patching::Policy::Patch.skip_assess_analysis?

            with_contrast_scope do
              return ret unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))

              shift = 0
              separator_length = separator.nil? ? 0 : separator.to_s.length
              parent_events = []
              ary.each do |obj|
                if obj # skip nil here
                  properties.copy_from(obj, ret, shift)
                  shift += obj.to_s.length
                  parent_event = Contrast::Agent::Assess::Tracker.properties(obj)&.event
                  parent_events << parent_event if parent_event
                end
                shift += separator_length
              end
              return ret unless Contrast::Agent::Assess::Tracker.tracked?(ret)

              properties.cleanup_tags
              properties.build_event(
                  ARRAY_JOIN_NODE,
                  ret,
                  ary,
                  ret,
                  [separator])
              properties.event.instance_variable_set(:@_parent_events, parent_events)
              ret
            end
          end

          def instrument_array_track
            @_instrument_array_track ||= begin
              require 'cs__assess_array/cs__assess_array'
              true
            end
          rescue StandardError, LoadError => e
            logger.error('Error loading assess track patch', e)
            false
          end
        end
      end
    end
  end
end