# typed: true
# frozen_string_literal: true

module Tapioca
  module Runtime
    module Trackers
      module Autoload
        extend Tracker
        extend T::Sig

        NOOP_METHOD = ->(*_args, **_kwargs, &_block) {}

        @constant_names_registered_for_autoload = T.let([], T::Array[String])

        class << self
          extend T::Sig

          sig { void }
          def eager_load_all!
            with_disabled_exits do
              until @constant_names_registered_for_autoload.empty?
                # Grab the next constant name
                constant_name = T.must(@constant_names_registered_for_autoload.shift)
                # Trigger autoload by constantizing the registered name
                Reflection.constantize(constant_name, inherit: true)
              end
            end
          end

          sig { params(constant_name: String).void }
          def register(constant_name)
            return unless enabled?

            @constant_names_registered_for_autoload << constant_name
          end

          sig do
            type_parameters(:Result)
              .params(block: T.proc.returns(T.type_parameter(:Result)))
              .returns(T.type_parameter(:Result))
          end
          def with_disabled_exits(&block)
            original_abort = Kernel.instance_method(:abort)
            original_exit = Kernel.instance_method(:exit)

            begin
              Kernel.define_method(:abort, NOOP_METHOD)
              Kernel.define_method(:exit, NOOP_METHOD)

              block.call
            ensure
              Kernel.define_method(:exit, original_exit)
              Kernel.define_method(:abort, original_abort)
            end
          end
        end
      end
    end
  end
end

# We need to do the alias-method-chain dance since Bootsnap does the same,
# and prepended modules and alias-method-chain don't play well together.
#
# So, why does Bootsnap do alias-method-chain and not prepend? Glad you asked!
# That's because RubyGems does alias-method-chain for Kernel#require and such,
# so, if Bootsnap were to do prepend, it might end up breaking RubyGems.
class Module
  alias_method(:autoload_without_tapioca, :autoload)

  def autoload(const_name, path)
    Tapioca::Runtime::Trackers::Autoload.register("#{self}::#{const_name}")
    autoload_without_tapioca(const_name, path)
  end
end