# frozen_string_literal: true

require 'pathname'
require 'test_helper'

class TestCpathExpectedAtErrors < LoaderTest
  test 'raises Zeitwerk::Error if the argument does not exist' do
    with_setup(dirs: ['.']) do
      error = assert_raises Zeitwerk::Error do
        loader.cpath_expected_at('does_not_exist.rb')
      end
      abspath = File.expand_path('does_not_exist.rb')
      assert_includes error.message, "#{abspath} does not exist"
    end
  end

  test 'raises Zeitwerk::NameError if the argument does not yield a constant name' do
    files = [['foo-bar.rb', nil], ['1.rb', nil]]
    with_files(files) do
      loader.push_dir('.')

      error = assert_raises Zeitwerk::NameError do
        loader.cpath_expected_at(files[0][0])
      end
      assert_includes error.message, 'wrong constant name Foo-bar'

      error = assert_raises Zeitwerk::NameError do
        loader.cpath_expected_at(files[1][0])
      end
      assert_includes error.message, 'wrong constant name 1'
    end
  end

  test 'raises Zeitwerk::NameError if some intermediate segment does not yield a constant name' do
    with_files([['x/foo-bar/y/z.rb', nil]]) do
      loader.push_dir('.')
      error = assert_raises Zeitwerk::NameError do
        loader.cpath_expected_at('x/foo-bar/y/z.rb')
      end
      assert_includes error.message, 'wrong constant name Foo-bar'
    end
  end
end

class TestCpathExpectedAtNil < LoaderTest
  test 'returns nil if the argument is not a directory or Ruby file' do
    files = [['tasks/database.rake', nil], ['CHANGELOG', nil]]
    with_setup(files) do
      files.each do |file, _contents|
        assert_nil loader.cpath_expected_at(file)
      end
    end
  end

  test 'returns nil if the argument is ignored' do
    with_setup([['ignored.rb']]) do
      assert_nil loader.cpath_expected_at('ignored.rb')
    end
  end

  test 'returns nil if the argument is a hidden Ruby file' do
    with_setup([['.foo.rb']]) do
      assert_nil loader.cpath_expected_at('.foo.rb')
    end
  end

  test 'returns nil if the argument does not belong to the autoload paths' do
    with_setup(dirs: ['.']) do
      assert_nil loader.cpath_expected_at(__dir__)
      assert_nil loader.cpath_expected_at(__FILE__)
    end
  end

  test 'returns nil if an ancestor is ignored' do
    with_setup([['ignored/x.rb']]) do
      assert_nil loader.cpath_expected_at('ignored/x.rb')
    end
  end

  test 'returns nil if an ancestor is a hidden directory' do
    with_setup([['.foo/x.rb']]) do
      assert_nil loader.cpath_expected_at('.foo/x.rb')
    end
  end
end

class TestCpathExpectedAtString < LoaderTest
  module M
    def self.name
      'Overridden'
    end
  end

  M_REAL_NAME = "#{name}::M"

  test 'returns the name of the root namespace for a root directory (Object)' do
    with_setup([['README.md']]) do
      assert_equal 'Object', loader.cpath_expected_at('.')
    end
  end

  test 'returns the name of the root namespace for a root directory (Object, Pathname)' do
    with_setup([['README.md']]) do
      assert_equal 'Object', loader.cpath_expected_at(Pathname.new('.'))
    end
  end

  test 'returns the name of the root namespace for a root directory (Custom)' do
    with_setup(dirs: ['.'], namespace: M) do
      assert_equal M_REAL_NAME, loader.cpath_expected_at('.')
    end
  end

  test 'returns the name of the root namespace for a root directory (Custom, Pathname)' do
    with_setup(dirs: ['.'], namespace: M) do
      assert_equal M_REAL_NAME, loader.cpath_expected_at(Pathname.new('.'))
    end
  end

  test 'returns the name of the root directory even if it is hidden' do
    with_setup([['.foo/x.rb', nil]], dirs: ['.foo']) do
      assert_equal 'Object', loader.cpath_expected_at('.foo')
    end
  end

  test 'returns the cpath to a root file (Object)' do
    with_setup([['x.rb', 'X = 1']]) do
      assert_equal 'X', loader.cpath_expected_at('x.rb')
    end
  end

  test 'returns the cpath to a root file (Custom)' do
    with_setup([['x.rb', 'X = 1']], namespace: M) do
      assert_equal "#{M_REAL_NAME}::X", loader.cpath_expected_at('x.rb')
    end
  end

  test 'returns the cpath to a subdirectory (Object)' do
    with_setup([['a/x.rb', 'A::X = 1']]) do
      assert_equal 'A', loader.cpath_expected_at('a')
    end
  end

  test 'returns the cpath to a subdirectory (Custom)' do
    with_setup([['a/x.rb', 'A::X = 1']], namespace: M) do
      assert_equal "#{M_REAL_NAME}::A", loader.cpath_expected_at('a')
    end
  end

  test 'returns the cpath to a nested file (Object)' do
    with_setup([['a/b/c/x.rb', 'A::B::C::X = 1']]) do
      assert_equal 'A::B::C::X', loader.cpath_expected_at('a/b/c/x.rb')
    end
  end

  test 'returns the cpath to a nested file (Custom)' do
    with_setup([['a/b/c/x.rb', 'A::B::C::X = 1']], namespace: M) do
      assert_equal "#{M_REAL_NAME}::A::B::C::X", loader.cpath_expected_at('a/b/c/x.rb')
    end
  end

  test 'returns the cpath to a nested directory (Object)' do
    with_setup([['a/b/c/x.rb', 'A::B::C::X = 1']]) do
      assert_equal 'A::B::C', loader.cpath_expected_at('a/b/c')
    end
  end

  test 'returns the cpath to a nested directory (Custom)' do
    with_setup([['a/b/c/x.rb', 'A::B::C::X = 1']], namespace: M) do
      assert_equal "#{M_REAL_NAME}::A::B::C", loader.cpath_expected_at('a/b/c')
    end
  end

  test 'supports collapsed directories (Object)' do
    with_setup([['a/b/collapsed/x.rb', 'A::B::X = 1']]) do
      assert_equal 'A::B::X', loader.cpath_expected_at('a/b/collapsed/x.rb')
      assert_equal 'A::B', loader.cpath_expected_at('a/b/collapsed')
    end
  end

  test 'supports collapsed directories (Custom)' do
    with_setup([['a/b/collapsed/x.rb', 'A::B::X = 1']], namespace: M) do
      assert_equal "#{M_REAL_NAME}::A::B::X", loader.cpath_expected_at('a/b/collapsed/x.rb')
      assert_equal "#{M_REAL_NAME}::A::B", loader.cpath_expected_at('a/b/collapsed')
    end
  end

  test 'passes directory paths to the inflector' do
    loader.inflector = Class.new(Zeitwerk::Inflector) do
      def camelize(_basename, abspath)
        super(File.basename(abspath).delete_suffix('.rb'), abspath)
      end
    end.new

    with_setup([['a/b/x.rb', 'A::B::X = 1']]) do
      assert_equal 'A::B', loader.cpath_expected_at('a/b')
    end
  end
end
