// 
// Decompiled by Procyon v0.6.0
// 

package io.sentry;

import io.sentry.transport.RateLimiter;
import io.sentry.util.TracingUtils;
import java.util.Map;
import io.sentry.util.SpanUtils;
import io.sentry.clientreport.DiscardReason;
import io.sentry.protocol.SentryTransaction;
import java.util.List;
import io.sentry.protocol.User;
import java.util.Iterator;
import java.util.concurrent.RejectedExecutionException;
import java.io.Closeable;
import io.sentry.hints.SessionStartHint;
import io.sentry.util.HintUtils;
import io.sentry.hints.SessionEndHint;
import io.sentry.util.Objects;
import io.sentry.protocol.Feedback;
import io.sentry.protocol.SentryId;
import org.jetbrains.annotations.ApiStatus;
import io.sentry.logger.LoggerApi;
import io.sentry.logger.ILoggerApi;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;

public final class Scopes implements IScopes
{
    @NotNull
    private final IScope scope;
    @NotNull
    private final IScope isolationScope;
    @NotNull
    private final IScope globalScope;
    @Nullable
    private final Scopes parentScopes;
    @NotNull
    private final String creator;
    @NotNull
    private final CompositePerformanceCollector compositePerformanceCollector;
    @NotNull
    private final CombinedScopeView combinedScope;
    @NotNull
    private final ILoggerApi logger;
    
    public Scopes(@NotNull final IScope scope, @NotNull final IScope isolationScope, @NotNull final IScope globalScope, @NotNull final String creator) {
        this(scope, isolationScope, globalScope, null, creator);
    }
    
    private Scopes(@NotNull final IScope scope, @NotNull final IScope isolationScope, @NotNull final IScope globalScope, @Nullable final Scopes parentScopes, @NotNull final String creator) {
        this.combinedScope = new CombinedScopeView(globalScope, isolationScope, scope);
        this.scope = scope;
        this.isolationScope = isolationScope;
        this.globalScope = globalScope;
        this.parentScopes = parentScopes;
        this.creator = creator;
        final SentryOptions options = this.getOptions();
        validateOptions(options);
        this.compositePerformanceCollector = options.getCompositePerformanceCollector();
        this.logger = new LoggerApi(this);
    }
    
    @NotNull
    public String getCreator() {
        return this.creator;
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public IScope getScope() {
        return this.scope;
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public IScope getIsolationScope() {
        return this.isolationScope;
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public IScope getGlobalScope() {
        return this.globalScope;
    }
    
    @ApiStatus.Internal
    @Nullable
    @Override
    public IScopes getParentScopes() {
        return this.parentScopes;
    }
    
    @ApiStatus.Internal
    @Override
    public boolean isAncestorOf(@Nullable final IScopes otherScopes) {
        return otherScopes != null && (this == otherScopes || (otherScopes.getParentScopes() != null && this.isAncestorOf(otherScopes.getParentScopes())));
    }
    
    @NotNull
    @Override
    public IScopes forkedScopes(@NotNull final String creator) {
        return new Scopes(this.scope.clone(), this.isolationScope.clone(), this.globalScope, this, creator);
    }
    
    @NotNull
    @Override
    public IScopes forkedCurrentScope(@NotNull final String creator) {
        return new Scopes(this.scope.clone(), this.isolationScope, this.globalScope, this, creator);
    }
    
    @NotNull
    @Override
    public IScopes forkedRootScopes(@NotNull final String creator) {
        return Sentry.forkedRootScopes(creator);
    }
    
    @Override
    public boolean isEnabled() {
        return this.getClient().isEnabled();
    }
    
    @NotNull
    @Override
    public SentryId captureEvent(@NotNull final SentryEvent event, @Nullable final Hint hint) {
        return this.captureEventInternal(event, hint, null);
    }
    
    @NotNull
    @Override
    public SentryId captureEvent(@NotNull final SentryEvent event, @Nullable final Hint hint, @NotNull final ScopeCallback callback) {
        return this.captureEventInternal(event, hint, callback);
    }
    
    @NotNull
    private SentryId captureEventInternal(@NotNull final SentryEvent event, @Nullable final Hint hint, @Nullable final ScopeCallback scopeCallback) {
        SentryId sentryId = SentryId.EMPTY_ID;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureEvent' call is a no-op.", new Object[0]);
        }
        else if (event == null) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "captureEvent called with null parameter.", new Object[0]);
        }
        else {
            try {
                this.assignTraceContext(event);
                final IScope localScope = this.buildLocalScope(this.getCombinedScopeView(), scopeCallback);
                sentryId = this.getClient().captureEvent(event, localScope, hint);
                this.updateLastEventId(sentryId);
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing event with id: " + event.getEventId(), e);
            }
        }
        return sentryId;
    }
    
    @ApiStatus.Internal
    @NotNull
    public ISentryClient getClient() {
        return this.getCombinedScopeView().getClient();
    }
    
    private void assignTraceContext(@NotNull final SentryEvent event) {
        this.getCombinedScopeView().assignTraceContext(event);
    }
    
    @NotNull
    private IScope buildLocalScope(@NotNull final IScope parentScope, @Nullable final ScopeCallback callback) {
        if (callback != null) {
            try {
                final IScope localScope = parentScope.clone();
                callback.run(localScope);
                return localScope;
            }
            catch (final Throwable t) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error in the 'ScopeCallback' callback.", t);
            }
        }
        return parentScope;
    }
    
    @NotNull
    @Override
    public SentryId captureMessage(@NotNull final String message, @NotNull final SentryLevel level) {
        return this.captureMessageInternal(message, level, null);
    }
    
    @NotNull
    @Override
    public SentryId captureMessage(@NotNull final String message, @NotNull final SentryLevel level, @NotNull final ScopeCallback callback) {
        return this.captureMessageInternal(message, level, callback);
    }
    
    @NotNull
    private SentryId captureMessageInternal(@NotNull final String message, @NotNull final SentryLevel level, @Nullable final ScopeCallback scopeCallback) {
        SentryId sentryId = SentryId.EMPTY_ID;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureMessage' call is a no-op.", new Object[0]);
        }
        else if (message == null) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "captureMessage called with null parameter.", new Object[0]);
        }
        else {
            try {
                final IScope localScope = this.buildLocalScope(this.getCombinedScopeView(), scopeCallback);
                sentryId = this.getClient().captureMessage(message, level, localScope);
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing message: " + message, e);
            }
        }
        this.updateLastEventId(sentryId);
        return sentryId;
    }
    
    @NotNull
    @Override
    public SentryId captureFeedback(@NotNull final Feedback feedback, @Nullable final Hint hint, @Nullable final ScopeCallback scopeCallback) {
        SentryId sentryId = SentryId.EMPTY_ID;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureFeedback' call is a no-op.", new Object[0]);
        }
        else if (feedback.getMessage().isEmpty()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "captureFeedback called with empty message.", new Object[0]);
        }
        else {
            try {
                final IScope localScope = this.buildLocalScope(this.getCombinedScopeView(), scopeCallback);
                sentryId = this.getClient().captureFeedback(feedback, hint, localScope);
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing feedback: " + feedback.getMessage(), e);
            }
        }
        return sentryId;
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public SentryId captureEnvelope(@NotNull final SentryEnvelope envelope, @Nullable final Hint hint) {
        Objects.requireNonNull(envelope, "SentryEnvelope is required.");
        SentryId sentryId = SentryId.EMPTY_ID;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureEnvelope' call is a no-op.", new Object[0]);
        }
        else {
            try {
                final SentryId capturedEnvelopeId = this.getClient().captureEnvelope(envelope, hint);
                if (capturedEnvelopeId != null) {
                    sentryId = capturedEnvelopeId;
                }
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing envelope.", e);
            }
        }
        return sentryId;
    }
    
    @NotNull
    @Override
    public SentryId captureException(@NotNull final Throwable throwable, @Nullable final Hint hint) {
        return this.captureExceptionInternal(throwable, hint, null);
    }
    
    @NotNull
    @Override
    public SentryId captureException(@NotNull final Throwable throwable, @Nullable final Hint hint, @NotNull final ScopeCallback callback) {
        return this.captureExceptionInternal(throwable, hint, callback);
    }
    
    @NotNull
    private SentryId captureExceptionInternal(@NotNull final Throwable throwable, @Nullable final Hint hint, @Nullable final ScopeCallback scopeCallback) {
        SentryId sentryId = SentryId.EMPTY_ID;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureException' call is a no-op.", new Object[0]);
        }
        else if (throwable == null) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "captureException called with null parameter.", new Object[0]);
        }
        else {
            try {
                final SentryEvent event = new SentryEvent(throwable);
                this.assignTraceContext(event);
                final IScope localScope = this.buildLocalScope(this.getCombinedScopeView(), scopeCallback);
                sentryId = this.getClient().captureEvent(event, localScope, hint);
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing exception: " + throwable.getMessage(), e);
            }
        }
        this.updateLastEventId(sentryId);
        return sentryId;
    }
    
    @Override
    public void captureUserFeedback(@NotNull final UserFeedback userFeedback) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureUserFeedback' call is a no-op.", new Object[0]);
        }
        else {
            try {
                this.getClient().captureUserFeedback(userFeedback);
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing captureUserFeedback: " + userFeedback.toString(), e);
            }
        }
    }
    
    @Override
    public void startSession() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'startSession' call is a no-op.", new Object[0]);
        }
        else {
            final Scope.SessionPair pair = this.getCombinedScopeView().startSession();
            if (pair != null) {
                if (pair.getPrevious() != null) {
                    final Hint hint = HintUtils.createWithTypeCheckHint(new SessionEndHint());
                    this.getClient().captureSession(pair.getPrevious(), hint);
                }
                final Hint hint = HintUtils.createWithTypeCheckHint(new SessionStartHint());
                this.getClient().captureSession(pair.getCurrent(), hint);
            }
            else {
                this.getOptions().getLogger().log(SentryLevel.WARNING, "Session could not be started.", new Object[0]);
            }
        }
    }
    
    @Override
    public void endSession() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'endSession' call is a no-op.", new Object[0]);
        }
        else {
            final Session previousSession = this.getCombinedScopeView().endSession();
            if (previousSession != null) {
                final Hint hint = HintUtils.createWithTypeCheckHint(new SessionEndHint());
                this.getClient().captureSession(previousSession, hint);
            }
        }
    }
    
    @ApiStatus.Internal
    @NotNull
    public IScope getCombinedScopeView() {
        return this.combinedScope;
    }
    
    @Override
    public void close() {
        this.close(false);
    }
    
    @Override
    public void close(final boolean isRestarting) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'close' call is a no-op.", new Object[0]);
        }
        else {
            try {
                for (final Integration integration : this.getOptions().getIntegrations()) {
                    if (integration instanceof Closeable) {
                        try {
                            ((Closeable)integration).close();
                        }
                        catch (final Throwable e) {
                            this.getOptions().getLogger().log(SentryLevel.WARNING, "Failed to close the integration {}.", integration, e);
                        }
                    }
                }
                this.configureScope(scope -> scope.clear());
                this.configureScope(ScopeType.ISOLATION, scope -> scope.clear());
                this.getOptions().getBackpressureMonitor().close();
                this.getOptions().getTransactionProfiler().close();
                this.getOptions().getContinuousProfiler().close(true);
                this.getOptions().getCompositePerformanceCollector().close();
                this.getOptions().getConnectionStatusProvider().close();
                final ISentryExecutorService executorService = this.getOptions().getExecutorService();
                if (isRestarting) {
                    try {
                        executorService.submit(() -> executorService.close(this.getOptions().getShutdownTimeoutMillis()));
                    }
                    catch (final RejectedExecutionException e2) {
                        this.getOptions().getLogger().log(SentryLevel.WARNING, "Failed to submit executor service shutdown task during restart. Shutting down synchronously.", e2);
                        executorService.close(this.getOptions().getShutdownTimeoutMillis());
                    }
                }
                else {
                    executorService.close(this.getOptions().getShutdownTimeoutMillis());
                }
                this.configureScope(ScopeType.CURRENT, scope -> scope.getClient().close(isRestarting));
                this.configureScope(ScopeType.ISOLATION, scope -> scope.getClient().close(isRestarting));
                this.configureScope(ScopeType.GLOBAL, scope -> scope.getClient().close(isRestarting));
            }
            catch (final Throwable e3) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while closing the Scopes.", e3);
            }
        }
    }
    
    @Override
    public void addBreadcrumb(@NotNull final Breadcrumb breadcrumb, @Nullable final Hint hint) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'addBreadcrumb' call is a no-op.", new Object[0]);
        }
        else if (breadcrumb == null) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "addBreadcrumb called with null parameter.", new Object[0]);
        }
        else {
            this.getCombinedScopeView().addBreadcrumb(breadcrumb, hint);
        }
    }
    
    @Override
    public void addBreadcrumb(@NotNull final Breadcrumb breadcrumb) {
        this.addBreadcrumb(breadcrumb, new Hint());
    }
    
    @Override
    public void setLevel(@Nullable final SentryLevel level) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'setLevel' call is a no-op.", new Object[0]);
        }
        else {
            this.getCombinedScopeView().setLevel(level);
        }
    }
    
    @Override
    public void setTransaction(@Nullable final String transaction) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'setTransaction' call is a no-op.", new Object[0]);
        }
        else if (transaction != null) {
            this.getCombinedScopeView().setTransaction(transaction);
        }
        else {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Transaction cannot be null", new Object[0]);
        }
    }
    
    @Override
    public void setUser(@Nullable final User user) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'setUser' call is a no-op.", new Object[0]);
        }
        else {
            this.getCombinedScopeView().setUser(user);
        }
    }
    
    @Override
    public void setFingerprint(@NotNull final List<String> fingerprint) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'setFingerprint' call is a no-op.", new Object[0]);
        }
        else if (fingerprint == null) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "setFingerprint called with null parameter.", new Object[0]);
        }
        else {
            this.getCombinedScopeView().setFingerprint(fingerprint);
        }
    }
    
    @Override
    public void clearBreadcrumbs() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'clearBreadcrumbs' call is a no-op.", new Object[0]);
        }
        else {
            this.getCombinedScopeView().clearBreadcrumbs();
        }
    }
    
    @Override
    public void setTag(@Nullable final String key, @Nullable final String value) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'setTag' call is a no-op.", new Object[0]);
        }
        else if (key == null || value == null) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "setTag called with null parameter.", new Object[0]);
        }
        else {
            this.getCombinedScopeView().setTag(key, value);
        }
    }
    
    @Override
    public void removeTag(@Nullable final String key) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'removeTag' call is a no-op.", new Object[0]);
        }
        else if (key == null) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "removeTag called with null parameter.", new Object[0]);
        }
        else {
            this.getCombinedScopeView().removeTag(key);
        }
    }
    
    @Override
    public void setExtra(@Nullable final String key, @Nullable final String value) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'setExtra' call is a no-op.", new Object[0]);
        }
        else if (key == null || value == null) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "setExtra called with null parameter.", new Object[0]);
        }
        else {
            this.getCombinedScopeView().setExtra(key, value);
        }
    }
    
    @Override
    public void removeExtra(@Nullable final String key) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'removeExtra' call is a no-op.", new Object[0]);
        }
        else if (key == null) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "removeExtra called with null parameter.", new Object[0]);
        }
        else {
            this.getCombinedScopeView().removeExtra(key);
        }
    }
    
    private void updateLastEventId(@NotNull final SentryId lastEventId) {
        this.getCombinedScopeView().setLastEventId(lastEventId);
    }
    
    @NotNull
    @Override
    public SentryId getLastEventId() {
        return this.getCombinedScopeView().getLastEventId();
    }
    
    @Override
    public ISentryLifecycleToken pushScope() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'pushScope' call is a no-op.", new Object[0]);
            return NoOpScopesLifecycleToken.getInstance();
        }
        final IScopes scopes = this.forkedCurrentScope("pushScope");
        return scopes.makeCurrent();
    }
    
    @Override
    public ISentryLifecycleToken pushIsolationScope() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'pushIsolationScope' call is a no-op.", new Object[0]);
            return NoOpScopesLifecycleToken.getInstance();
        }
        final IScopes scopes = this.forkedScopes("pushIsolationScope");
        return scopes.makeCurrent();
    }
    
    @NotNull
    @Override
    public ISentryLifecycleToken makeCurrent() {
        return Sentry.setCurrentScopes(this);
    }
    
    @Deprecated
    @Override
    public void popScope() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'popScope' call is a no-op.", new Object[0]);
        }
        else {
            final Scopes parent = this.parentScopes;
            if (parent != null) {
                parent.makeCurrent();
            }
        }
    }
    
    @Override
    public void withScope(@NotNull final ScopeCallback callback) {
        if (!this.isEnabled()) {
            try {
                callback.run(NoOpScope.getInstance());
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error in the 'withScope' callback.", e);
            }
        }
        else {
            final IScopes forkedScopes = this.forkedCurrentScope("withScope");
            try (final ISentryLifecycleToken ignored = forkedScopes.makeCurrent()) {
                callback.run(forkedScopes.getScope());
            }
            catch (final Throwable e2) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error in the 'withScope' callback.", e2);
            }
        }
    }
    
    @Override
    public void withIsolationScope(@NotNull final ScopeCallback callback) {
        if (!this.isEnabled()) {
            try {
                callback.run(NoOpScope.getInstance());
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error in the 'withIsolationScope' callback.", e);
            }
        }
        else {
            final IScopes forkedScopes = this.forkedScopes("withIsolationScope");
            try (final ISentryLifecycleToken ignored = forkedScopes.makeCurrent()) {
                callback.run(forkedScopes.getIsolationScope());
            }
            catch (final Throwable e2) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error in the 'withIsolationScope' callback.", e2);
            }
        }
    }
    
    @Override
    public void configureScope(@Nullable final ScopeType scopeType, @NotNull final ScopeCallback callback) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'configureScope' call is a no-op.", new Object[0]);
        }
        else {
            try {
                callback.run(this.combinedScope.getSpecificScope(scopeType));
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error in the 'configureScope' callback.", e);
            }
        }
    }
    
    @Override
    public void bindClient(@NotNull final ISentryClient client) {
        if (client != null) {
            this.getOptions().getLogger().log(SentryLevel.DEBUG, "New client bound to scope.", new Object[0]);
            this.getCombinedScopeView().bindClient(client);
        }
        else {
            this.getOptions().getLogger().log(SentryLevel.DEBUG, "NoOp client bound to scope.", new Object[0]);
            this.getCombinedScopeView().bindClient(NoOpSentryClient.getInstance());
        }
    }
    
    @Override
    public boolean isHealthy() {
        return this.getClient().isHealthy();
    }
    
    @Override
    public void flush(final long timeoutMillis) {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'flush' call is a no-op.", new Object[0]);
        }
        else {
            try {
                this.getClient().flush(timeoutMillis);
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error in the 'client.flush'.", e);
            }
        }
    }
    
    @Deprecated
    @NotNull
    @Override
    public IHub clone() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Disabled Scopes cloned.", new Object[0]);
        }
        return new HubScopesWrapper(this.forkedScopes("scopes clone"));
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public SentryId captureTransaction(@NotNull final SentryTransaction transaction, @Nullable final TraceContext traceContext, @Nullable final Hint hint, @Nullable final ProfilingTraceData profilingTraceData) {
        Objects.requireNonNull(transaction, "transaction is required");
        SentryId sentryId = SentryId.EMPTY_ID;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureTransaction' call is a no-op.", new Object[0]);
        }
        else if (!transaction.isFinished()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Transaction: %s is not finished and this 'captureTransaction' call is a no-op.", transaction.getEventId());
        }
        else if (!Boolean.TRUE.equals(transaction.isSampled())) {
            this.getOptions().getLogger().log(SentryLevel.DEBUG, "Transaction %s was dropped due to sampling decision.", transaction.getEventId());
            if (this.getOptions().getBackpressureMonitor().getDownsampleFactor() > 0) {
                this.getOptions().getClientReportRecorder().recordLostEvent(DiscardReason.BACKPRESSURE, DataCategory.Transaction);
                this.getOptions().getClientReportRecorder().recordLostEvent(DiscardReason.BACKPRESSURE, DataCategory.Span, transaction.getSpans().size() + 1);
            }
            else {
                this.getOptions().getClientReportRecorder().recordLostEvent(DiscardReason.SAMPLE_RATE, DataCategory.Transaction);
                this.getOptions().getClientReportRecorder().recordLostEvent(DiscardReason.SAMPLE_RATE, DataCategory.Span, transaction.getSpans().size() + 1);
            }
        }
        else {
            try {
                sentryId = this.getClient().captureTransaction(transaction, traceContext, this.getCombinedScopeView(), hint, profilingTraceData);
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing transaction with id: " + transaction.getEventId(), e);
            }
        }
        return sentryId;
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public SentryId captureProfileChunk(@NotNull final ProfileChunk profilingContinuousData) {
        Objects.requireNonNull(profilingContinuousData, "profilingContinuousData is required");
        SentryId sentryId = SentryId.EMPTY_ID;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureTransaction' call is a no-op.", new Object[0]);
        }
        else {
            try {
                sentryId = this.getClient().captureProfileChunk(profilingContinuousData, this.getScope());
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing profile chunk with id: " + profilingContinuousData.getChunkId(), e);
            }
        }
        return sentryId;
    }
    
    @NotNull
    @Override
    public ITransaction startTransaction(@NotNull final TransactionContext transactionContext, @NotNull final TransactionOptions transactionOptions) {
        return this.createTransaction(transactionContext, transactionOptions);
    }
    
    @NotNull
    private ITransaction createTransaction(@NotNull final TransactionContext transactionContext, @NotNull final TransactionOptions transactionOptions) {
        Objects.requireNonNull(transactionContext, "transactionContext is required");
        transactionContext.setOrigin(transactionOptions.getOrigin());
        ITransaction transaction;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'startTransaction' returns a no-op.", new Object[0]);
            transaction = NoOpTransaction.getInstance();
        }
        else if (SpanUtils.isIgnored(this.getOptions().getIgnoredSpanOrigins(), transactionContext.getOrigin())) {
            this.getOptions().getLogger().log(SentryLevel.DEBUG, "Returning no-op for span origin %s as the SDK has been configured to ignore it", transactionContext.getOrigin());
            transaction = NoOpTransaction.getInstance();
        }
        else if (!this.getOptions().getInstrumenter().equals(transactionContext.getInstrumenter())) {
            this.getOptions().getLogger().log(SentryLevel.DEBUG, "Returning no-op for instrumenter %s as the SDK has been configured to use instrumenter %s", transactionContext.getInstrumenter(), this.getOptions().getInstrumenter());
            transaction = NoOpTransaction.getInstance();
        }
        else if (!this.getOptions().isTracingEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.INFO, "Tracing is disabled and this 'startTransaction' returns a no-op.", new Object[0]);
            transaction = NoOpTransaction.getInstance();
        }
        else {
            final Double sampleRand = this.getSampleRand(transactionContext);
            final SamplingContext samplingContext = new SamplingContext(transactionContext, transactionOptions.getCustomSamplingContext(), sampleRand, null);
            final TracesSampler tracesSampler = this.getOptions().getInternalTracesSampler();
            final TracesSamplingDecision samplingDecision = tracesSampler.sample(samplingContext);
            transactionContext.setSamplingDecision(samplingDecision);
            final ISpanFactory maybeSpanFactory = transactionOptions.getSpanFactory();
            final ISpanFactory spanFactory = (maybeSpanFactory == null) ? this.getOptions().getSpanFactory() : maybeSpanFactory;
            if (samplingDecision.getSampled() && this.getOptions().isContinuousProfilingEnabled() && this.getOptions().getProfileLifecycle() == ProfileLifecycle.TRACE && transactionContext.getProfilerId().equals(SentryId.EMPTY_ID)) {
                this.getOptions().getContinuousProfiler().startProfiler(ProfileLifecycle.TRACE, this.getOptions().getInternalTracesSampler());
            }
            transaction = spanFactory.createTransaction(transactionContext, this, transactionOptions, this.compositePerformanceCollector);
            if (samplingDecision.getSampled() && samplingDecision.getProfileSampled()) {
                final ITransactionProfiler transactionProfiler = this.getOptions().getTransactionProfiler();
                if (!transactionProfiler.isRunning()) {
                    transactionProfiler.start();
                    transactionProfiler.bindTransaction(transaction);
                }
                else if (transactionOptions.isAppStartTransaction()) {
                    transactionProfiler.bindTransaction(transaction);
                }
            }
        }
        if (transactionOptions.isBindToScope()) {
            transaction.makeCurrent();
        }
        return transaction;
    }
    
    @NotNull
    private Double getSampleRand(@NotNull final TransactionContext transactionContext) {
        final Baggage baggage = transactionContext.getBaggage();
        if (baggage != null) {
            final Double sampleRandFromBaggageMaybe = baggage.getSampleRand();
            if (sampleRandFromBaggageMaybe != null) {
                return sampleRandFromBaggageMaybe;
            }
        }
        return this.getCombinedScopeView().getPropagationContext().getSampleRand();
    }
    
    @Override
    public void startProfiler() {
        if (this.getOptions().isContinuousProfilingEnabled()) {
            if (this.getOptions().getProfileLifecycle() != ProfileLifecycle.MANUAL) {
                this.getOptions().getLogger().log(SentryLevel.WARNING, "Profiling lifecycle is %s. Profiling cannot be started manually.", this.getOptions().getProfileLifecycle().name());
                return;
            }
            this.getOptions().getContinuousProfiler().startProfiler(ProfileLifecycle.MANUAL, this.getOptions().getInternalTracesSampler());
        }
        else if (this.getOptions().isProfilingEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Continuous Profiling is not enabled. Set profilesSampleRate and profilesSampler to null to enable it.", new Object[0]);
        }
    }
    
    @Override
    public void stopProfiler() {
        if (this.getOptions().isContinuousProfilingEnabled()) {
            if (this.getOptions().getProfileLifecycle() != ProfileLifecycle.MANUAL) {
                this.getOptions().getLogger().log(SentryLevel.WARNING, "Profiling lifecycle is %s. Profiling cannot be stopped manually.", this.getOptions().getProfileLifecycle().name());
                return;
            }
            this.getOptions().getLogger().log(SentryLevel.DEBUG, "Stopped continuous Profiling.", new Object[0]);
            this.getOptions().getContinuousProfiler().stopProfiler(ProfileLifecycle.MANUAL);
        }
        else {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Continuous Profiling is not enabled. Set profilesSampleRate and profilesSampler to null to enable it.", new Object[0]);
        }
    }
    
    @ApiStatus.Internal
    @Override
    public void setSpanContext(@NotNull final Throwable throwable, @NotNull final ISpan span, @NotNull final String transactionName) {
        this.getCombinedScopeView().setSpanContext(throwable, span, transactionName);
    }
    
    @Nullable
    @Override
    public ISpan getSpan() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'getSpan' call is a no-op.", new Object[0]);
            return null;
        }
        return this.getCombinedScopeView().getSpan();
    }
    
    @Override
    public void setActiveSpan(@Nullable final ISpan span) {
        this.getCombinedScopeView().setActiveSpan(span);
    }
    
    @ApiStatus.Internal
    @Nullable
    @Override
    public ITransaction getTransaction() {
        ITransaction span = null;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'getTransaction' call is a no-op.", new Object[0]);
        }
        else {
            span = this.getCombinedScopeView().getTransaction();
        }
        return span;
    }
    
    @NotNull
    @Override
    public SentryOptions getOptions() {
        return this.combinedScope.getOptions();
    }
    
    @Nullable
    @Override
    public Boolean isCrashedLastRun() {
        return SentryCrashLastRunState.getInstance().isCrashedLastRun(this.getOptions().getCacheDirPath(), !this.getOptions().isEnableAutoSessionTracking());
    }
    
    @Override
    public void reportFullyDisplayed() {
        if (this.getOptions().isEnableTimeToFullDisplayTracing()) {
            this.getOptions().getFullyDisplayedReporter().reportFullyDrawn();
        }
    }
    
    @Nullable
    @Override
    public TransactionContext continueTrace(@Nullable final String sentryTrace, @Nullable final List<String> baggageHeaders) {
        final PropagationContext propagationContext = PropagationContext.fromHeaders(this.getOptions().getLogger(), sentryTrace, baggageHeaders);
        this.configureScope(scope -> scope.withPropagationContext(oldPropagationContext -> scope.setPropagationContext(propagationContext)));
        if (this.getOptions().isTracingEnabled()) {
            return TransactionContext.fromPropagationContext(propagationContext);
        }
        return null;
    }
    
    @Nullable
    @Override
    public SentryTraceHeader getTraceparent() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'getTraceparent' call is a no-op.", new Object[0]);
        }
        else {
            final TracingUtils.TracingHeaders headers = TracingUtils.trace(this, null, this.getSpan());
            if (headers != null) {
                return headers.getSentryTraceHeader();
            }
        }
        return null;
    }
    
    @Nullable
    @Override
    public BaggageHeader getBaggage() {
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'getBaggage' call is a no-op.", new Object[0]);
        }
        else {
            final TracingUtils.TracingHeaders headers = TracingUtils.trace(this, null, this.getSpan());
            if (headers != null) {
                return headers.getBaggageHeader();
            }
        }
        return null;
    }
    
    @NotNull
    @Override
    public SentryId captureCheckIn(@NotNull final CheckIn checkIn) {
        SentryId sentryId = SentryId.EMPTY_ID;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureCheckIn' call is a no-op.", new Object[0]);
        }
        else {
            try {
                sentryId = this.getClient().captureCheckIn(checkIn, this.getCombinedScopeView(), null);
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing check-in for slug", e);
            }
        }
        this.updateLastEventId(sentryId);
        return sentryId;
    }
    
    @NotNull
    @Override
    public SentryId captureReplay(@NotNull final SentryReplayEvent replay, @Nullable final Hint hint) {
        SentryId sentryId = SentryId.EMPTY_ID;
        if (!this.isEnabled()) {
            this.getOptions().getLogger().log(SentryLevel.WARNING, "Instance is disabled and this 'captureReplay' call is a no-op.", new Object[0]);
        }
        else {
            try {
                sentryId = this.getClient().captureReplayEvent(replay, this.getCombinedScopeView(), hint);
            }
            catch (final Throwable e) {
                this.getOptions().getLogger().log(SentryLevel.ERROR, "Error while capturing replay", e);
            }
        }
        return sentryId;
    }
    
    @ApiStatus.Internal
    @Nullable
    @Override
    public RateLimiter getRateLimiter() {
        return this.getClient().getRateLimiter();
    }
    
    @NotNull
    @Override
    public ILoggerApi logger() {
        return this.logger;
    }
    
    @Override
    public void addFeatureFlag(@Nullable final String flag, @Nullable final Boolean result) {
        this.combinedScope.addFeatureFlag(flag, result);
    }
    
    private static void validateOptions(@NotNull final SentryOptions options) {
        Objects.requireNonNull(options, "SentryOptions is required.");
        if (options.getDsn() == null || options.getDsn().isEmpty()) {
            throw new IllegalArgumentException("Scopes requires a DSN to be instantiated. Considering using the NoOpScopes if no DSN is available.");
        }
    }
}
