<?php

namespace App\Support;

use Sentry\State\Scope;
use Throwable;

class SentryReporter
{
    private static ?\WeakMap $reported = null;

    /**
     * @param  array{tags?: array<string, string|int|null>, extra?: array<string, mixed>}  $context
     */
    public static function captureException(Throwable $exception, array $context = []): void
    {
        if (! self::shouldReport()) {
            return;
        }

        if (self::wasReported($exception)) {
            return;
        }

        self::markReported($exception);

        if (! function_exists('\Sentry\withScope')) {
            app('sentry')->captureException($exception);

            return;
        }

        \Sentry\withScope(function (Scope $scope) use ($exception, $context): void {
            self::applyContext($scope, $context);
            app('sentry')->captureException($exception);
        });
    }

    private static function shouldReport(): bool
    {
        return app()->bound('sentry') && ! empty(config('sentry.dsn'));
    }

    private static function wasReported(Throwable $exception): bool
    {
        $reported = self::reportedMap();

        return $reported->offsetExists($exception);
    }

    private static function markReported(Throwable $exception): void
    {
        $reported = self::reportedMap();
        $reported[$exception] = true;
    }

    private static function reportedMap(): \WeakMap
    {
        if (! self::$reported instanceof \WeakMap) {
            self::$reported = new \WeakMap;
        }

        return self::$reported;
    }

    /**
     * @param  array{tags?: array<string, string|int|null>, extra?: array<string, mixed>}  $context
     */
    private static function applyContext(Scope $scope, array $context): void
    {
        foreach (($context['tags'] ?? []) as $key => $value) {
            if ($value === null || $value === '') {
                continue;
            }

            $scope->setTag((string) $key, (string) $value);
        }

        foreach (($context['extra'] ?? []) as $key => $value) {
            if ($value === null) {
                continue;
            }

            $scope->setExtra((string) $key, $value);
        }
    }
}
