# frozen_string_literal: true

# Copyright The OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

module OpenTelemetry
  module SDK
    module Trace
      module Samplers
        # @api private
        #
        # Implements consistent sampling based on a probability.
        class ConsistentProbabilityBased
          include ConsistentProbabilityTraceState

          attr_reader :description

          def initialize(probability)
            if probability < 2e-62
              @p_floor = 63
              @p_ceil = @p_ceil_probability = 0
              @description = 'ConsistentProbabilityBased{0}'
              return
            end

            @p_floor = (Math.frexp(probability)[1] - 1).abs
            @p_ceil = @p_floor - 1
            floor = Math.ldexp(1.0, -@p_floor)
            ceil = Math.ldexp(1.0, -@p_ceil)
            @p_ceil_probability = (probability - floor) / (ceil - floor)
            @description = format('ConsistentProbabilityBased{%.6f}', probability)
          end

          def ==(other)
            @description == other.description
          end

          # @api private
          #
          # See {Samplers}.
          def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:)
            parent_span_context = OpenTelemetry::Trace.current_span(parent_context).context
            p = probabilistic_p
            tracestate = parent_span_context.tracestate
            parse_ot_vendor_tag(tracestate) do |_, r, rest|
              if r.nil? || r > 62
                OpenTelemetry.logger.debug("ConsistentProbabilitySampler: potentially inconsistent trace detected - r: #{r.inspect}") if parent_span_context.valid?
                r = generate_r(trace_id)
              end
              if p <= r
                Result.new(decision: Decision::RECORD_AND_SAMPLE, tracestate: update_tracestate(tracestate, p, r, rest))
              else
                Result.new(decision: Decision::DROP, tracestate: update_tracestate(tracestate, nil, r, rest))
              end
            end
          end

          private

          def probabilistic_p
            if Random.rand < @p_ceil_probability
              @p_ceil
            else
              @p_floor
            end
          end
        end
      end
    end
  end
end
