class Dependabot::Uv::FileParser

def blocking_marker?(dep)

def blocking_marker?(dep)
  return false if dep["markers"] == "None"
  marker = dep["markers"]
  version = python_raw_version
  if marker.include?("python_version")
    !marker_satisfied?(marker, version)
  else
    return true if dep["markers"].include?("<")
    return false if dep["markers"].include?(">")
    return false if dep["requirement"].nil?
    dep["requirement"].include?("<")
  end
end

def check_required_files

def check_required_files
  filenames = dependency_files.map(&:name)
  return if filenames.any? { |name| name.end_with?(".txt", ".in") }
  return if pyproject
  raise "Missing required files!"
end

def check_requirements(requirements)

def check_requirements(requirements)
  requirements.each do |dep|
    next unless dep["requirement"]
    Requirement.new(dep["requirement"].split(","))
  rescue Gem::Requirement::BadRequirementError => e
    raise DependencyFileNotEvaluatable, e.message
  end
end

def detect_uv_version

def detect_uv_version
  version = uv_version.to_s.split("version ").last&.split(")")&.first
  log_if_version_malformed("uv", version)
  version if version&.match?(/^\d+(?:\.\d+)*$/)
rescue StandardError
  nil
end

def detected_package_manager

def detected_package_manager
  setup_python_environment if Experiments.enabled?(:enable_file_parser_python_local)
  PackageManager.new(T.must(detect_uv_version))
end

def ecosystem

def ecosystem
  @ecosystem ||= T.let(
    Ecosystem.new(
      name: ECOSYSTEM,
      package_manager: package_manager,
      language: language
    ),
    T.nilable(Ecosystem)
  )
end

def evaluate_condition(condition, python_version)

def evaluate_condition(condition, python_version)
  operator, version = condition.match(/([<>=!]=?)\s*"?([\d.]+)"?/)&.captures
  case operator
  when "<"
    Version.new(python_version) < Version.new(version)
  when "<="
    Version.new(python_version) <= Version.new(version)
  when ">"
    Version.new(python_version) > Version.new(version)
  when ">="
    Version.new(python_version) >= Version.new(version)
  when "=="
    Version.new(python_version) == Version.new(version)
  else
    false
  end
end

def group_from_filename(filename)

def group_from_filename(filename)
  if filename.include?("dev") then ["dev-dependencies"]
  else
    ["dependencies"]
  end
end

def language

def language
  Language.new(
    detected_version: python_raw_version,
    raw_version: python_command_version
  )
end

def language_version_manager

def language_version_manager
  @language_version_manager ||= T.let(LanguageVersionManager.new(python_requirement_parser:
                                  python_requirement_parser), T.nilable(LanguageVersionManager))
end

def log_if_version_malformed(package_manager, version)

def log_if_version_malformed(package_manager, version)
  if version.match?(/^\d+(?:\.\d+)*$/)
    true
  else
    Dependabot.logger.warn("Detected #{package_manager} with malformed version #{version}")
    false
  end
end

def marker_satisfied?(marker, python_version)

def marker_satisfied?(marker, python_version)
  conditions = marker.split(/\s+(and|or)\s+/)
  result = T.let(evaluate_condition(conditions.shift, python_version), T::Boolean)
  until conditions.empty?
    operator = conditions.shift
    next_condition = conditions.shift
    next_result = evaluate_condition(next_condition, python_version)
    result = if operator == "and"
               result && next_result
             else
               result || next_result
             end
  end
  result
end

def normalised_name(name, extras = [])

def normalised_name(name, extras = [])
  NameNormaliser.normalise_including_extras(name, extras)
end

def old_pyyaml?(name, version)

def old_pyyaml?(name, version)
  major_version = version&.split(".")&.first
  return false unless major_version
  name == "pyyaml" && major_version < "6"
end

def package_manager

def package_manager
  if Experiments.enabled?(:enable_file_parser_python_local)
    Dependabot.logger.info("Detected package manager : #{detected_package_manager.name}")
  end
  @package_manager ||= T.let(detected_package_manager, T.nilable(Ecosystem::VersionManager))
end

def parse

def parse
  dependency_set = DependencySet.new
  dependency_set += pyproject_file_dependencies if pyproject
  dependency_set += requirement_dependencies if requirement_files.any?
  dependency_set.dependencies
end

def parsed_requirement_files

def parsed_requirement_files
  SharedHelpers.in_a_temporary_directory do
    write_temporary_dependency_files
    requirements = SharedHelpers.run_helper_subprocess(
      command: "pyenv exec python3 #{NativeHelpers.python_helper_path}",
      function: "parse_requirements",
      args: [Dir.pwd]
    )
    check_requirements(requirements)
    requirements
  end
rescue SharedHelpers::HelperSubprocessFailed => e
  evaluation_errors = REQUIREMENT_FILE_EVALUATION_ERRORS
  raise unless e.message.start_with?(*evaluation_errors)
  raise DependencyFileNotEvaluatable, e.message
end

def pyproject

def pyproject
  @pyproject ||= T.let(get_original_file("pyproject.toml"), T.nilable(DependencyFile))
end

def pyproject_file_dependencies

def pyproject_file_dependencies
  @pyproject_file_dependencies ||= T.let(PyprojectFilesParser.new(dependency_files:
                                    dependency_files).dependency_set, T.nilable(DependencySet))
end

def python_command_version

def python_command_version
  language_version_manager.installed_version
end

def python_raw_version

def python_raw_version
  if Experiments.enabled?(:enable_file_parser_python_local)
    Dependabot.logger.info("Detected python version: #{language_version_manager.python_version}")
    Dependabot.logger.info("Detected python major minor version: #{language_version_manager.python_major_minor}")
  end
  language_version_manager.python_version
end

def python_requirement_parser

def python_requirement_parser
  @python_requirement_parser ||= T.let(PythonRequirementParser.new(dependency_files:
                                   dependency_files), T.nilable(PythonRequirementParser))
end

def remove_imports(file)

def remove_imports(file)
  return file.content if file.path.end_with?(".tar.gz", ".whl", ".zip")
  file.content.lines
      .reject { |l| l.match?(/^['"]?(?<path>\..*?)(?=\[|#|'|"|$)/) }
      .reject { |l| l.match?(/^(?:-e)\s+['"]?(?<path>.*?)(?=\[|#|'|"|$)/) }
      .join
end

def requirement_dependencies

def requirement_dependencies
  dependencies = DependencySet.new
  parsed_requirement_files.each do |dep|
    next if blocking_marker?(dep)
    name = dep["name"]
    file = dep["file"]
    version = dep["version"]
    original_file = get_original_file(file)
    requirements =
      if original_file && requirements_in_file_matcher.compiled_file?(original_file) then []
      else
        [{
          requirement: dep["requirement"],
          file: Pathname.new(file).cleanpath.to_path,
          source: nil,
          groups: group_from_filename(file)
        }]
      end
    # PyYAML < 6.0 will cause `pip-compile` to fail due to incompatibility with Cython 3. Workaround it. PR #8189
    SharedHelpers.run_shell_command("pyenv exec pip install cython<3.0") if old_pyyaml?(name, version)
    dependencies <<
      Dependency.new(
        name: normalised_name(name, dep["extras"]),
        version: version&.include?("*") ? nil : version,
        requirements: requirements,
        package_manager: "uv"
      )
  end
  dependencies
end

def requirement_files

def requirement_files
  dependency_files.select { |f| f.name.end_with?(".txt", ".in") }
end

def requirements_in_file_matcher

def requirements_in_file_matcher
  @requirements_in_file_matcher ||= T.let(RequiremenstFileMatcher.new(requirements_in_files),
                                          T.nilable(RequiremenstFileMatcher))
end

def requirements_in_files

def requirements_in_files
  @requirements_in_files ||= T.let(dependency_files.select { |f| f.name.end_with?(".in") }, T.untyped)
end

def setup_python_environment

def setup_python_environment
  language_version_manager.install_required_python
  SharedHelpers.run_shell_command("pyenv local #{language_version_manager.python_major_minor}")
rescue StandardError => e
  Dependabot.logger.error(e.message)
  nil
end

def uv_version

def uv_version
  version_info = SharedHelpers.run_shell_command("pyenv exec uv --version")
  Dependabot.logger.info("Package manager uv, Info : #{version_info}")
  version_info.match(/\d+(?:\.\d+)*/)&.to_s
rescue StandardError => e
  Dependabot.logger.error(e.message)
  nil
end

def write_temporary_dependency_files

def write_temporary_dependency_files
  dependency_files
    .reject { |f| f.name == ".python-version" }
    .each do |file|
      path = file.name
      FileUtils.mkdir_p(Pathname.new(path).dirname)
      File.write(path, remove_imports(file))
    end
end