module RBS
  class TypeNameResolver
    Query = _ = Struct.new(:type_name, :context, keyword_init: true)

    attr_reader :all_names
    attr_reader :cache

    def initialize()
      @all_names = Set[]
      @cache = {}
    end

    def self.from_env(env)
      new.add_names(env.class_decls.keys)
        .add_names(env.interface_decls.keys)
        .add_names(env.alias_decls.keys)
    end

    def add_names(names)
      all_names.merge(names)
      self
    end

    def try_cache(query)
      cache.fetch(query) do
        result = yield
        cache[query] = result
      end
    end

    def resolve(type_name, context:)
      if type_name.absolute?
        return type_name
      end

      query = Query.new(type_name: type_name, context: context)
      try_cache(query) do
        path_head, *path_tail = type_name.to_namespace.path
        raise unless path_head

        name_head = TypeName.new(name: path_head, namespace: Namespace.empty)

        absolute_head = context.find do |namespace|
          # @type break: TypeName
          full_name = name_head.with_prefix(namespace)
          has_name?(full_name) and break full_name
        end

        case absolute_head
        when TypeName
          has_name?(Namespace.new(path: absolute_head.to_namespace.path.push(*path_tail), absolute: true).to_type_name)
        when Namespace
          # This cannot happen because the `context.find` doesn't return a Namespace.
          raise
        end
      end
    end

    def has_name?(full_name)
      if all_names.include?(full_name)
        full_name
      end
    end
  end
end
