# frozen_string_literal: true

require "core/extension"
require "core/local"
require "core/state"

module Core
  # [public] Customized inspections for any object.
  #
  module Inspect
    require_relative "inspect/inspection"
    require_relative "inspect/version"

    extend Core::Extension

    extends dependencies: [Core::State]

    extends :definition do
      # [public] Sets one or more instance variables or instance methods as inspectable.
      #
      # without - An array of instance variables or instance methods that should not be inspected.
      #
      def inspects(*inspectables, without: [])
        if inspectables.any?
          mutate_state :__inspectables__ do |current_inspectables|
            inspectables.each do |inspectable|
              inspection = build_inspection(inspectable)
              current_inspectables[inspection.name] = inspection
            end

            current_inspectables
          end
        end

        if without.any?
          mutate_state :__uninspectables__ do |current_uninspectables|
            without.each do |inspectable|
              inspection = build_inspection(inspectable)
              current_uninspectables[inspection.name] = inspection
            end

            current_uninspectables
          end
        end
      end

      private def build_inspection(inspectable)
        case inspectable
        when Core::Inspect::Inspection
          inspectable
        else
          Core::Inspect::Inspection.new(name: inspectable)
        end
      end
    end

    applies do
      state :__inspectables__, default: {}
      state :__uninspectables__, default: {}
    end

    # [public] Inspects the object.
    #
    def inspect
      inspection = +"#<#{self.class}:0x#{__id__.to_s(16)}"

      if Inspect.inspecting?(self)
        "#{inspection} ...>"
      else
        Inspect.prevent_recursion(self) do
          each_inspectable do |inspectable|
            inspection << ", #{inspectable.name}=#{inspectable.resolve(self).inspect}"
          end

          inspection.strip << ">"
        end
      end
    end

    private def each_inspectable
      inspectables = if @__inspectables__.any?
        @__inspectables__
      else
        instance_variables.map(&:to_s).reject { |instance_variable|
          instance_variable.start_with?("@_")
        }.each_with_object({}) { |instance_variable, hash|
          hash[instance_variable] = self.class.__send__(:build_inspection, instance_variable)
        }
      end

      inspectables.each_value do |inspectable|
        unless @__uninspectables__.include?(inspectable.name)
          yield inspectable
        end
      end
    end

    class << self
      include Core::Local

      def prevent_recursion(object)
        object_id = object.object_id
        inspected_objects[object_id] = true
        yield
      ensure
        inspected_objects.delete(object_id)
      end

      def inspecting?(object)
        inspected_objects[object.object_id]
      end

      def inspected_objects
        localized(:__corerb_inspected_objects__) || localize(:__corerb_inspected_objects__, {})
      end
    end
  end
end