# frozen_string_literal: true

require_relative "base"

module SimpleCov
  module ParallelAdapters
    # Adapter for [grosser/parallel_tests](https://github.com/grosser/parallel_tests).
    # This is the historical default — SimpleCov has special-cased
    # parallel_tests since 0.18 — and remains the most precise option for
    # projects on it. Detection requires the full native coordination
    # contract: the `ParallelTests` constant has been loaded,
    # `TEST_ENV_NUMBER` is set, and `PARALLEL_PID_FILE` is set. The pid-file
    # path is required because the native wait API reads it with `ENV.fetch`.
    # When a runner only provides the env-var convention, GenericAdapter is
    # the correct coordination path.
    class ParallelTestsAdapter < Base
      class << self
        def active?
          return false if SimpleCov.parallel_tests == false

          ensure_loaded
          # !! to coerce `defined?` (returns nil or "constant") to a proper bool.
          !!(defined?(::ParallelTests) && native_parallel_tests_environment?)
        end

        # Pick the *first* started process to do the final-result work,
        # not the last. The parallel_tests README recommends
        # `first_process?` for "do something once after every worker
        # finishes" hooks, so user code that has its own
        # `wait_for_other_processes_to_finish` in an `RSpec.after(:suite)`
        # overwhelmingly waits in the first process — picking the same
        # side avoids the cross-process deadlock #922 reported. Also
        # handles `PARALLEL_TEST_GROUPS=1` naturally (the only worker's
        # `TEST_ENV_NUMBER` is "" and `first_process?` tests for that
        # empty string).
        def first_worker?
          ::ParallelTests.first_process?
        end

        def wait_for_siblings
          return unless native_parallel_tests_environment?

          ::ParallelTests.wait_for_other_processes_to_finish
        end

        # The native wait blocks until every sibling process exits, but only
        # when the pid-file contract is present (see `wait_for_siblings`).
        def native_wait?
          native_parallel_tests_environment?
        end

        def expected_worker_count
          ENV["PARALLEL_TEST_GROUPS"]&.to_i || 1
        end

        # Auto-require `parallel_tests` when it's installed AND the env
        # vars it sets are present, so callers can rely on
        # `defined?(::ParallelTests)` downstream. parallel_tests is an
        # optional dependency (see https://github.com/grosser/parallel_tests/issues/772),
        # and `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` are commonly set
        # for other reasons (custom subprocess coordination, CI sharding,
        # the parallel_rspec gem which intentionally mirrors the env-var
        # convention), so a missing gem is treated as "user isn't using
        # parallel_tests" — silently skip and let GenericAdapter handle
        # it. Users who want to override the auto-detect can set
        # `SimpleCov.parallel_tests true` (force on) or `false` (force
        # off). See #1018.
        def ensure_loaded
          return if defined?(::ParallelTests) # simplecov:disable — only true after a previous load
          return if SimpleCov.parallel_tests == false # simplecov:disable — only fires when user opts out
          # simplecov:disable — env-var-only path
          return unless SimpleCov.parallel_tests || env_suggests_parallel_tests?

          # simplecov:disable — only fires under a real parallel_tests setup
          require "parallel_tests"
        rescue LoadError
          # Gem isn't installed; stay quiet — warning here regressed
          # users who use those env vars for their own subprocess
          # coordination.
          # simplecov:enable
        end

        def env_suggests_parallel_tests?
          ENV.key?("TEST_ENV_NUMBER") && ENV.key?("PARALLEL_TEST_GROUPS")
        end

        def native_parallel_tests_environment?
          ENV.key?("TEST_ENV_NUMBER") && ENV.key?("PARALLEL_PID_FILE")
        end
      end
    end
  end
end
