# frozen_string_literal: true

require 'pathname'
require 'test_helper'

class TestEagerLoadDir < LoaderTest
  test 'eager loads all files' do
    files = [
      ['x.rb', 'X = 1'],
      ['y.rb', 'Y = 1'],
      ['m/n/p.rb', 'module M::N::P; end'],
      ['m/n/a.rb', 'M::N::A = 1'],
      ['m/n/p/q/z.rb', 'M::N::P::Q::Z = 1']
    ]
    with_setup(files) do
      loader.eager_load_dir('.')

      files.each do |file|
        assert required?(file)
      end
    end
  end

  test 'eager loads all files (Pathname)' do
    files = [
      ['x.rb', 'X = 1'],
      ['y.rb', 'Y = 1'],
      ['m/n/p.rb', 'module M::N::P; end'],
      ['m/n/a.rb', 'M::N::A = 1'],
      ['m/n/p/q/z.rb', 'M::N::P::Q::Z = 1']
    ]
    with_setup(files) do
      loader.eager_load_dir(Pathname.new('.'))
      assert required?(files)
    end
  end

  test 'does not eager load excluded files or directories' do
    files = [
      ['x.rb', 'X = 1'],
      ['y.rb', 'Y = 1'],
      ['m/n.rb', 'module M::N; end'],
      ['m/n/a.rb', 'M::N::A = 1']
    ]
    with_setup(files) do
      loader.do_not_eager_load('y.rb')
      loader.do_not_eager_load('m/n')
      loader.eager_load_dir('.')

      assert required?(files[0])
      assert !required?(files[1])
      assert required?(files[2])
      assert !required?(files[3])
    end
  end

  test 'does not eager load excluded files or directories (descendants)' do
    files = [
      ['excluded/m/n.rb', 'module M::N; end'],
      ['excluded/m/n/a.rb', 'M::N::A = 1']
    ]
    with_setup(files) do
      loader.do_not_eager_load('excluded')
      loader.eager_load_dir('.')

      assert files.none? { |file| required?(file) }
    end
  end

  # This is a file system-based interface.
  test 'eager loads excluded explicit namespaces if some subtree is not excluded' do
    files = [
      ['x.rb', 'X = 1'],
      ['m/n.rb', 'module M::N; end'],
      ['m/n/a.rb', 'M::N::A = 1']
    ]
    with_setup(files) do
      loader.do_not_eager_load('m/n.rb')
      loader.eager_load_dir('.')

      assert required?(files)
    end
  end

  test 'does not load intermediate files if the target is an excluded descendant' do
    files = [['excluded.rb', 'module Excluded; end'], ['excluded/n/x.rb', 'EXCLUDED']]
    with_setup(files) do
      loader.do_not_eager_load('excluded')
      loader.eager_load_dir('excluded/n')

      assert !required?(files[0])
    end
  end

  test 'does not eager load descendant ignored files or directories' do
    files = [
      ['x.rb', 'X = 1'],
      ['y.rb', 'IGNORED'],
      ['m/n.rb', 'module M::N; end'],
      ['m/n/a.rb', 'IGNORED']
    ]
    with_files(files) do
      loader.push_dir('.')
      loader.ignore('y.rb')
      loader.ignore('m/n')
      loader.setup
      loader.eager_load_dir('.')

      assert required?(files[0])
      assert !required?(files[1])
      assert required?(files[2])
      assert !required?(files[3])
    end
  end

  test 'does not eager load shadowed files' do
    files = [
      ['rd1/x.rb', 'X = 1'],
      ['rd2/x.rb', 'SHADOWED']
    ]
    with_setup(files) do
      loader.eager_load_dir('rd2')

      assert !required?(files[0])
      assert !required?(files[1])
    end
  end

  test 'eager loads all files in a subdirectory, ignoring what is above' do
    files = [
      ['x.rb', 'IGNORED'],
      ['m/k/x.rb', 'IGNORED'],
      ['m/n/p.rb', 'module M::N::P; end'],
      ['m/n/a.rb', 'M::N::A = 1'],
      ['m/n/p/q/z.rb', 'M::N::P::Q::Z = 1']
    ]
    with_setup(files) do
      loader.eager_load_dir('m/n')

      assert !required?(files[0])
      assert !required?(files[1])
      assert required?(files[2])
      assert required?(files[3])
      assert required?(files[4])
    end
  end

  test 'eager loads all files, ignoring other directories (different namespace)' do
    files = [
      ['a/x.rb', 'A::X = 1'],
      ['b/y.rb', 'B::Y = 1'],
      ['c/z.rb', 'C::Z = 1']
    ]
    with_setup(files) do
      loader.eager_load_dir('a')

      assert required?(files[0])
      assert !required?(files[1])
      assert !required?(files[2])
    end
  end

  test 'eager loads all files, ignoring other directories (same namespace)' do
    files = [
      ['rd1/m/x.rb', 'M::X = 1'],
      ['rd2/m/y.rb', 'M::Y = 1'],
    ]
    with_setup(files) do
      loader.eager_load_dir('rd1/m')

      assert required?(files[0])
      assert !required?(files[1])
    end
  end

  # This is a file system-based interface.
  test 'eager loads collapsed directories, ignoring the rest of the namespace' do
    files = [['x.rb', 'X = 1'], ['collapsed/y.rb', 'Y = 1']]
    with_setup(files) do
      loader.eager_load_dir('collapsed')

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'does not eager load ignored directories (same)' do
    files = [['ignored/x.rb', 'IGNORED']]
    with_setup(files) do
      loader.eager_load_dir('ignored')

      assert !required?(files[0])
    end
  end

  test 'does not eager load if the argument is an ignored directory (descendant)' do
    files = [['ignored/m/x.rb', 'IGNORED']]
    with_setup(files) do
      loader.eager_load_dir('ignored/m')

      assert !required?(files[0])
    end
  end

  test 'files under nested root directories are ignored' do
    files = [
      ['x.rb', 'X = 1'],
      ['nested_root/y.rb', 'Y = 1']
    ]
    with_setup(files, dirs: %w(. nested_root)) do
      loader.eager_load_dir('.')

      assert required?(files[0])
      assert !required?(files[1])
    end
  end

  test 'files under a root directory are loaded even if it has an ignored ascendant' do
    files = [
      ['x.rb', 'X = 1'],
      ['ignored/x.rb', 'IGNORED'],
      ['ignored/nested-rd/y.rb', 'Y = 1']
    ]
    with_setup(files, dirs: %w(. ignored/nested-rd)) do
      loader.eager_load_dir('ignored/nested-rd')

      assert !required?(files[0])
      assert !required?(files[1])
      assert required?(files[2])
    end
  end

  test 'does not eager load hidden directories' do
    files = [['a/x.rb', 'X = 1'], ['a/.b/c/x.rb', nil]]
    with_setup(files) do
      loader.eager_load_dir('a/.b')
      assert !required?(files[0])

      loader.eager_load_dir('a/.b/c')
      assert !required?(files[0])
    end
  end

  test 'can be called recursively' do
    $test_loader = loader
    files = [
      ['a/x.rb', "A::X = 1; $test_loader.eager_load_dir('b')"],
      ['b/x.rb', 'B::X = 1']
    ]
    with_setup(files) do
      loader.eager_load_dir('a')

      assert files.all? { |file| required?(file) }
    end
  end

  test 'does not prevent reload' do
    $test_loaded_count = 0
    files = [['m/x.rb', '$test_loaded_count += 1; M::X = 1']]
    with_setup(files) do
      loader.eager_load_dir('m')
      assert_equal 1, $test_loaded_count

      loader.reload

      loader.eager_load_dir('m')
      assert_equal 2, $test_loaded_count
    end
  end

  test 'non-Ruby files are just ignored' do
    files = [
      ['x.rb', 'X = 1'],
      ['README.md', ''],
      ['TODO.txt', ''],
      ['.config', ''],
    ]
    with_setup(files) do
      loader.eager_load_dir('.')

      assert required?(files[0])
    end
  end

  test 'shortcircuits if eager loaded' do
    files = [['x.rb', 'X = 1']]
    with_setup(files) do
      loader.eager_load

      # Dirty way to prove we shortcircuit.
      def loader.actual_eager_load_dir(*)
        raise
      end

      begin
        loader.eager_load_dir('.')
      rescue
        flunk
      else
        pass
      end
    end
  end

  test 'raises Zeitwerk::Error if the argument is not a directory' do
    with_setup do
      e = assert_raises(Zeitwerk::Error) { loader.eager_load_dir(__FILE__) }
      assert_equal "#{__FILE__} is not a directory", e.message
    end
  end

  test 'raises if the argument is not managed by the loader' do
    with_setup do
      e = assert_raises(Zeitwerk::Error) { loader.eager_load_dir(__dir__) }
      assert_equal "I do not manage #{__dir__}", e.message
    end
  end

  test 'does not inflect ancestor directories if the argument is unmanaged' do
    called = false

    loader.inflector = Class.new(Zeitwerk::Inflector) do
      define_method(:camelize) do |basename, abspath|
        called = true
        super(basename, abspath)
      end
    end.new

    with_setup do
      called = false

      e = assert_raises(Zeitwerk::Error) { loader.eager_load_dir(__dir__) }
      assert_equal "I do not manage #{__dir__}", e.message
      assert !called
    end
  end

  test 'raises if called before setup' do
    assert_raises(Zeitwerk::SetupRequired) do
      loader.eager_load_dir(__dir__)
    end
  end
end
