# frozen_string_literal: true

module Yardcheck
  class Observation
    include Concord.new(:documentation, :event)

    def violations
      param_violations + return_violations + raise_violations
    end

    def source_code
      documentation.source
    end

    def source_location
      documentation.location_pointer
    end

    def test_location
      event.example_location
    end

    def test_id
      event.example_id
    end

    def method_shorthand
      documentation.shorthand
    end

    def documented_param(name)
      documentation.params.fetch(name)
    end

    def observed_param(name)
      event.params.fetch(name)
    end

    def documented_return_type
      documentation.return_type
    end

    def documented_raise_type
      documentation.raise_type
    end

    def actual_return_type
      event.return_value.type
    end

    def actual_raise_type
      event.exception.type
    end

    def documentation_warnings
      documentation.warnings
    end

    private

    def param_violations
      overlapping_keys = documentation.params.keys & event.params.keys

      overlapping_keys.map do |key|
        type_definition = documentation.params.fetch(key)
        test_value      = event.params.fetch(key)

        next if type_definition.match?(test_value)
        Violation::Param.new(key, self)
      end
    end

    def return_violations
      invalid_return? ? [Violation::Return.new(self)] : []
    end

    def raise_violations
      invalid_raise? ? [Violation::Raise.new(self)] : []
    end

    def invalid_raise?
      documentation.raise_type &&
        event.raise?           &&
        !documentation.raise_type.match?(event.exception)
    end

    def invalid_return?
      documentation.return_type                               &&
        event.return?                                         &&
        !documentation.return_type.match?(event.return_value) &&
        !event.initialize?                                    &&
        !documentation.predicate_method?                      &&
        !possible_tracepoint_bug?
    end

    def possible_tracepoint_bug?
      return false unless event.return_value.is?(nil)

      documentation.processed_source.tracepoint_bug_candidate?
    end
  end # Observation
end # Yardcheck
