hex/lib/dependabot/hex/file_updater/mixfile_updater.rb
# typed: strict
# frozen_string_literal: true

require "sorbet-runtime"
require "dependabot/hex/file_updater"
require "dependabot/hex/file_updater/mixfile_requirement_updater"
require "dependabot/hex/file_updater/mixfile_git_pin_updater"

module Dependabot
  module Hex
    class FileUpdater
      class MixfileUpdater
        extend T::Sig

        sig { params(mixfile: Dependabot::DependencyFile, dependencies: T::Array[Dependabot::Dependency]).void }
        def initialize(mixfile:, dependencies:)
          @mixfile = T.let(mixfile, Dependabot::DependencyFile)
          @dependencies = T.let(dependencies, T::Array[Dependabot::Dependency])
        end

        sig { returns(String) }
        def updated_mixfile_content
          dependencies
            .select { |dep| requirement_changed?(mixfile, dep) }
            .reduce(T.must(mixfile.content).dup) do |content, dep|
              updated_content = content

              updated_content = update_requirement(
                content: updated_content,
                filename: mixfile.name,
                dependency: dep
              )

              updated_content = update_git_pin(
                content: updated_content,
                filename: mixfile.name,
                dependency: dep
              )

              raise "Expected content to change!" if content == updated_content

              updated_content
            end
        end

        private

        sig { returns(Dependabot::DependencyFile) }
        attr_reader :mixfile

        sig { returns(T::Array[Dependabot::Dependency]) }
        attr_reader :dependencies

        sig { params(file: Dependabot::DependencyFile, dependency: Dependabot::Dependency).returns(T::Boolean) }
        def requirement_changed?(file, dependency)
          changed_requirements =
            dependency.requirements - (dependency.previous_requirements || [])

          changed_requirements.any? { |f| f[:file] == file.name }
        end

        sig { params(content: String, filename: String, dependency: Dependabot::Dependency).returns(String) }
        def update_requirement(content:, filename:, dependency:)
          updated_req =
            dependency.requirements.find { |r| r[:file] == filename }
                      &.fetch(:requirement)

          old_req =
            dependency.previous_requirements
                      &.find { |r| r[:file] == filename }
                      &.fetch(:requirement)

          return content unless old_req

          MixfileRequirementUpdater.new(
            dependency_name: dependency.name,
            mixfile_content: content,
            previous_requirement: old_req,
            updated_requirement: updated_req
          ).updated_content
        end

        sig { params(content: String, filename: String, dependency: Dependabot::Dependency).returns(String) }
        def update_git_pin(content:, filename:, dependency:)
          updated_pin =
            dependency.requirements.find { |r| r[:file] == filename }
                      &.dig(:source, :ref)

          old_pin =
            dependency.previous_requirements
                      &.find { |r| r[:file] == filename }
                      &.dig(:source, :ref)

          return content unless old_pin
          return content if old_pin == updated_pin

          MixfileGitPinUpdater.new(
            dependency_name: dependency.name,
            mixfile_content: content,
            previous_pin: old_pin,
            updated_pin: updated_pin
          ).updated_content
        end
      end
    end
  end
end
