# frozen_string_literal: true

##
# ActiveModelSerializers::Logging
#
#   https://github.com/rails/rails/blob/280654ef88/activejob/lib/active_job/logging.rb
#
module ActiveModelSerializers
  module Logging
    RENDER_EVENT = 'render.active_model_serializers'.freeze
    extend ActiveSupport::Concern

    included do
      include ActiveModelSerializers::Callbacks
      extend Macros
      instrument_rendering
    end

    module ClassMethods
      def instrument_rendering
        around_render do |args, block|
          tag_logger do
            notify_render do
              block.call(args)
            end
          end
        end
      end
    end

    # Macros that can be used to customize the logging of class or instance methods,
    # by extending the class or its singleton.
    #
    # Adapted from:
    #   https://github.com/rubygems/rubygems/blob/cb28f5e991/lib/rubygems/deprecate.rb
    #
    # Provides a single method +notify+ to be used to declare when
    # something a method notifies, with the argument +callback_name+ of the notification callback.
    #
    #     class Adapter
    #       def self.klass_method
    #         # ...
    #       end
    #
    #       def instance_method
    #         # ...
    #       end
    #
    #       include ActiveModelSerializers::Logging::Macros
    #       notify :instance_method, :render
    #
    #       class << self
    #         extend ActiveModelSerializers::Logging::Macros
    #         notify :klass_method, :render
    #       end
    #     end
    module Macros
      ##
      # Simple notify method that wraps up +name+
      # in a dummy method. It notifies on with the +callback_name+ notifier on
      # each call to the dummy method, telling what the current serializer and adapter
      # are being rendered.
      # Adapted from:
      #   https://github.com/rubygems/rubygems/blob/cb28f5e991/lib/rubygems/deprecate.rb
      def notify(name, callback_name)
        class_eval do
          old = "_notifying_#{callback_name}_#{name}"
          alias_method old, name
          define_method name do |*args, &block|
            run_callbacks callback_name do
              send old, *args, &block
            end
          end
        end
      end
    end

    def notify_render(*)
      event_name = RENDER_EVENT
      ActiveSupport::Notifications.instrument(event_name, notify_render_payload) do
        yield
      end
    end

    def notify_render_payload
      {
        serializer: serializer || ActiveModel::Serializer::Null,
        adapter: adapter || ActiveModelSerializers::Adapter::Null
      }
    end

    private

    def tag_logger(*tags)
      if ActiveModelSerializers.logger.respond_to?(:tagged)
        tags.unshift 'active_model_serializers'.freeze unless logger_tagged_by_active_model_serializers?
        ActiveModelSerializers.logger.tagged(*tags) { yield }
      else
        yield
      end
    end

    def logger_tagged_by_active_model_serializers?
      ActiveModelSerializers.logger.formatter.current_tags.include?('active_model_serializers'.freeze)
    end

    class LogSubscriber < ActiveSupport::LogSubscriber
      def render(event)
        info do
          serializer = event.payload[:serializer]
          adapter = event.payload[:adapter]
          duration = event.duration.round(2)
          "Rendered #{serializer.name} with #{adapter.class} (#{duration}ms)"
        end
      end

      def logger
        ActiveModelSerializers.logger
      end
    end
  end
end

ActiveModelSerializers::Logging::LogSubscriber.attach_to :active_model_serializers
