class Steep::Services::SignatureService

def self.load_from(loader)

def self.load_from(loader)
  env = RBS::Environment.from_loader(loader).resolve_type_names
  new(env: env)
end

def add_descendants(graph:, names:, set:)

def add_descendants(graph:, names:, set:)
  set.merge(names)
  names.each do |name|
    case
    when name.interface?
      graph.each_descendant(RBS::AncestorGraph::InstanceNode.new(type_name: name)) do |node|
        set << node.type_name
      end
    when name.class?
      graph.each_descendant(RBS::AncestorGraph::InstanceNode.new(type_name: name)) do |node|
        set << node.type_name
      end
      graph.each_descendant(RBS::AncestorGraph::SingletonNode.new(type_name: name)) do |node|
        set << node.type_name
      end
    end
  end
end

def add_nested_decls(env:, names:, set:)

def add_nested_decls(env:, names:, set:)
  tops = names.each.with_object(Set[]) do |name, tops|
    unless name.namespace.empty?
      tops << name.namespace.path[0]
    end
  end
  env.class_decls.each_key do |name|
    unless name.namespace.empty?
      if tops.include?(name.namespace.path[0])
        set << name
      end
    end
  end
  env.interface_decls.each_key do |name|
    unless name.namespace.empty?
      if tops.include?(name.namespace.path[0])
        set << name
      end
    end
  end
end

def apply_changes(files, changes)

def apply_changes(files, changes)
  Steep.logger.tagged "#apply_changes" do
    Steep.measure2 "Applying change" do |sampler|
      changes.each.with_object({}) do |(path, cs), update|
        sampler.sample "#{path}" do
          old_text = files[path]&.content
          content = cs.inject(old_text || "") {|text, change| change.apply_to(text) }
          buffer = RBS::Buffer.new(name: path, content: content)
          update[path] = begin
                           FileStatus.new(path: path, content: content, decls: RBS::Parser.parse_signature(buffer))
                         rescue ArgumentError => exn
                           error = Diagnostic::Signature::UnexpectedError.new(
                             message: exn.message,
                             location: RBS::Location.new(buffer: buffer, start_pos: 0, end_pos: content.size)
                           )
                           FileStatus.new(path: path, content: content, decls: error)
                         rescue RBS::ParsingError => exn
                           FileStatus.new(path: path, content: content, decls: exn)
                         end
        end
      end
    end
  end
end

def const_decls(paths:, env:)

def const_decls(paths:, env:)
  env.constant_decls.filter do |_, entry|
    if location = entry.decl.location
      paths.include?(Pathname(location.buffer.name))
    end
  end
end

def current_subtyping

def current_subtyping
  if status.is_a?(LoadedStatus)
    status.subtyping
  end
end

def each_rbs_path(&block)

def each_rbs_path(&block)
  if block
    env_rbs_paths.each do |path|
      unless files.key?(path)
        yield path
      end
    end
    files.each_key(&block)
  else
    enum_for :each_rbs_path
  end
end

def env_rbs_paths

def env_rbs_paths
  @env_rbs_paths ||= latest_env.buffers.each.with_object(Set[]) do |buffer, set|
    set << Pathname(buffer.name)
  end
end

def files

def files
  status.files
end

def global_decls(paths:, env: latest_env)

def global_decls(paths:, env: latest_env)
  env.global_decls.filter do |_, entry|
    if location = entry.decl.location
      paths.include?(Pathname(location.buffer.name))
    end
  end
end

def initialize(env:)

def initialize(env:)
  builder = RBS::DefinitionBuilder.new(env: env)
  @status = LoadedStatus.new(builder: builder, files: {})
end

def latest_builder

def latest_builder
  case status
  when LoadedStatus
    status.builder
  when SyntaxErrorStatus, AncestorErrorStatus
    status.last_builder
  end
end

def latest_env

def latest_env
  latest_builder.env
end

def latest_rbs_index

def latest_rbs_index
  status.rbs_index
end

def pending_changed_paths

def pending_changed_paths
  case status
  when LoadedStatus
    Set[]
  when SyntaxErrorStatus, AncestorErrorStatus
    Set.new(status.changed_paths)
  end
end

def rescue_rbs_error(errors)

def rescue_rbs_error(errors)
  begin
    yield
  rescue RBS::ErrorBase => exn
    errors << exn
  end
end

def type_name_from_decl(decl, set:)

def type_name_from_decl(decl, set:)
  case decl
  when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module, RBS::AST::Declarations::Interface
    set << decl.name
    decl.members.each do |member|
      if member.is_a?(RBS::AST::Declarations::Base)
        type_name_from_decl(member, set: set)
      end
    end
  when RBS::AST::Declarations::Alias
    set << decl.name
  end
end

def type_names(paths:, env:)

def type_names(paths:, env:)
  env.declarations.each.with_object(Set[]) do |decl, set|
    if decl.location
      if paths.include?(Pathname(decl.location.buffer.name))
        type_name_from_decl(decl, set: set)
      end
    end
  end
end

def update(changes)

def update(changes)
  Steep.logger.tagged "#update" do
    updates = apply_changes(files, changes)
    paths = Set.new(updates.each_key)
    paths.merge(pending_changed_paths)
    if updates.each_value.any? {|file| !file.decls.is_a?(Array) }
      diagnostics = []
      updates.each_value do |file|
        unless file.decls.is_a?(Array)
          diagnostic = if file.decls.is_a?(Diagnostic::Signature::Base)
                         file.decls
                       else
                         # factory is not used here because the error is a syntax error.
                         Diagnostic::Signature.from_rbs_error(file.decls, factory: nil)
                       end
          diagnostics << diagnostic
        end
      end
      @status = SyntaxErrorStatus.new(
        files: self.files.merge(updates),
        diagnostics: diagnostics,
        last_builder: latest_builder,
        changed_paths: paths
      )
    else
      files = self.files.merge(updates)
      updated_files = paths.each.with_object({}) do |path, hash|
        hash[path] = files[path]
      end
      result =
        Steep.measure "#update_env with updated #{paths.size} files" do
          update_env(updated_files, paths: paths)
        end
      @status = case result
                when Array
                  AncestorErrorStatus.new(
                    changed_paths: paths,
                    last_builder: latest_builder,
                    diagnostics: result,
                    files: files
                  )
                when RBS::DefinitionBuilder::AncestorBuilder
                  builder2 = update_builder(ancestor_builder: result, paths: paths)
                  LoadedStatus.new(builder: builder2, files: files)
                end
    end
  end
end

def update_builder(ancestor_builder:, paths:)

def update_builder(ancestor_builder:, paths:)
  Steep.measure "#update_builder with #{paths.size} files" do
    changed_names = Set[]
    old_definition_builder = latest_builder
    old_env = old_definition_builder.env
    old_names = type_names(paths: paths, env: old_env)
    old_ancestor_builder = old_definition_builder.ancestor_builder
    old_graph = RBS::AncestorGraph.new(env: old_env, ancestor_builder: old_ancestor_builder)
    add_descendants(graph: old_graph, names: old_names, set: changed_names)
    add_nested_decls(env: old_env, names: old_names, set: changed_names)
    new_env = ancestor_builder.env
    new_ancestor_builder = ancestor_builder
    new_names = type_names(paths: paths, env: new_env)
    new_graph = RBS::AncestorGraph.new(env: new_env, ancestor_builder: new_ancestor_builder)
    add_descendants(graph: new_graph, names: new_names, set: changed_names)
    add_nested_decls(env: new_env, names: new_names, set: changed_names)
    old_definition_builder.update(
      env: new_env,
      ancestor_builder: new_ancestor_builder,
      except: changed_names
    )
  end
end

def update_env(updated_files, paths:)

def update_env(updated_files, paths:)
  Steep.logger.tagged "#update_env" do
    errors = []
    new_decls = Set[].compare_by_identity
    env =
      Steep.measure "Deleting out of date decls" do
        latest_env.reject do |decl|
          if decl.location
            paths.include?(decl.location.buffer.name)
          end
        end
      end
    Steep.measure "Loading new decls" do
      updated_files.each_value do |content|
        case decls = content.decls
        when RBS::ErrorBase
          errors << content.decls
        else
          begin
            content.decls.each do |decl|
              env << decl
              new_decls << decl
            end
          rescue RBS::LoadingError => exn
            errors << exn
          end
        end
      end
    end
    Steep.measure "validate type params" do
      begin
        env.validate_type_params
      rescue RBS::LoadingError => exn
        errors << exn
      end
    end
    unless errors.empty?
      return errors.map {|error|
        # Factory will not be used because of the possible error types.
        Diagnostic::Signature.from_rbs_error(error, factory: nil)
      }
    end
    Steep.measure "resolve type names with #{new_decls.size} top-level decls" do
      env = env.resolve_type_names(only: new_decls)
    end
    builder = RBS::DefinitionBuilder::AncestorBuilder.new(env: env)
    Steep.measure("Pre-loading one ancestors") do
      builder.env.class_decls.each_key do |type_name|
        rescue_rbs_error(errors) { builder.one_instance_ancestors(type_name) }
        rescue_rbs_error(errors) { builder.one_singleton_ancestors(type_name) }
      end
      builder.env.interface_decls.each_key do |type_name|
        rescue_rbs_error(errors) { builder.one_interface_ancestors(type_name) }
      end
    end
    unless errors.empty?
      # Builder won't be used.
      factory = AST::Types::Factory.new(builder: nil)
      return errors.map {|error| Diagnostic::Signature.from_rbs_error(error, factory: factory) }
    end
    builder
  end
end