# frozen_string_literal: true

class SingleEventLogsListener
  include BCDD::Result::EventLogs::Listener

  # A listener will be initialized before the first event log, and it is discarded after the last one.
  def initialize
    @buffer = []
  end

  # This method will be called before each event log block.
  # The parent event log block will be called first in the case of nested blocks.
  #
  # @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
  def on_start(scope:)
    scope => { id:, name:, desc: }

    @buffer << [id, "##{id} #{name} - #{desc}".chomp('- ')]
  end

  # This method will wrap all the event_logs in the same block.
  # It can be used to perform an instrumentation (measure/report) of the event_logs.
  #
  # @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
  def around_event_logs(scope:)
    yield
  end

  # This method will wrap each and_then call.
  # It can be used to perform an instrumentation (measure/report) of the and_then calls.
  #
  # @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
  # @param and_then:
  #  {:type=>:block, :arg=>:some_injected_value}
  #  {:type=>:method, :arg=>:some_injected_value, :method_name=>:some_method_name}
  def around_and_then(scope:, and_then:)
    yield
  end

  # This method will be called after each result recording/tracking.
  #
  # @param record:
  # {
  #   :root => {:id=>0, :name=>"RootOperation", :desc=>nil},
  #   :parent => {:id=>0, :name=>"RootOperation", :desc=>nil},
  #   :current => {:id=>1, :name=>"SomeOperation", :desc=>nil},
  #   :result => {:kind=>:success, :type=>:_continue_, :value=>{some: :thing}, :source=><MyProcess:0x0000000102fd6378>},
  #   :and_then => {:type=>:method, :arg=>nil, :method_name=>:some_method},
  #   :time => 2024-01-26 02:53:11.310431 UTC
  # }
  def on_record(record:)
    record => { current: { id: }, result: { kind:, type: } }

    method_name = record.dig(:and_then, :method_name)

    @buffer << [id, " * #{kind}(#{type}) from method: #{method_name}".chomp('from method: ')]
  end

  MapNestedMessages = ->(event_logs, buffer, hide_given_and_continue) do
    ids_level_parent = event_logs.dig(:metadata, :ids, :level_parent)

    messages = buffer.filter_map { |(id, msg)| "#{'   ' * ids_level_parent[id].first}#{msg}" if ids_level_parent[id] }

    messages.reject! { _1.match?(/\(_(given|continue)_\)/) } if hide_given_and_continue

    messages
  end

  # This method will be called at the end of the event_logs tracking.
  #
  # @param event_logs:
  # {
  #   :version => 1,
  #   :metadata => {
  #     :duration => 0,
  #     :trace_id => nil,
  #     :ids => {
  #       :tree => [0, [[1, []], [2, []]]],
  #       :matrix => { 0 => [0, 0], 1 => [1, 1], 2 => [2, 1]},
  #       :level_parent => { 0 => [0, 0], 1 => [1, 0], 2 => [1, 0]}
  #     }
  #   },
  #   :records => [
  #     # ...
  #   ]
  # }
  def on_finish(event_logs:)
    messages = MapNestedMessages[event_logs, @buffer, ENV['HIDE_GIVEN_AND_CONTINUE']]

    puts messages.join("\n")
  end

  # This method will be called when an exception is raised during the event_logs tracking.
  #
  # @param exception: Exception
  # @param event_logs: Hash
  def before_interruption(exception:, event_logs:)
    messages = MapNestedMessages[event_logs, @buffer, ENV['HIDE_GIVEN_AND_CONTINUE']]

    puts messages.join("\n")

    bc = ::ActiveSupport::BacktraceCleaner.new
    bc.add_filter { |line| line.gsub(__dir__.sub('/lib', ''), '').sub(/\A\//, '')}
    bc.add_silencer { |line| /lib\/bcdd\/result/.match?(line) }
    bc.add_silencer { |line| line.include?(RUBY_VERSION) }

    dir = "#{FileUtils.pwd[1..]}/"

    listener_filename = File.basename(__FILE__).chomp('.rb')

    cb = bc.clean(exception.backtrace)
    cb.each { _1.sub!(dir, '') }
    cb.reject! { _1.match?(/block \(\d levels?\) in|in `block in|internal:kernel|#{listener_filename}/) }

    puts "\nException:\n  #{exception.message} (#{exception.class})\n\nBacktrace:\n  #{cb.join("\n  ")}"
  end
end