<?php

declare(strict_types=1);

namespace App\Framework\CircuitBreaker\FailurePredicate;

use Throwable;

/**
 * Predicate that uses a callback function for custom logic
 */
final readonly class CallbackFailurePredicate implements FailurePredicate
{
    /**
     * @param callable(Throwable, string): bool $callback
     */
    public function __construct(
        private mixed $callback,
        private string $identifier = 'callback',
        private string $description = 'Custom callback predicate',
    ) {
        if (! is_callable($this->callback)) {
            throw new \InvalidArgumentException('Callback must be callable');
        }
    }

    public function shouldTrigger(Throwable $exception, string $service): bool
    {
        try {
            return (bool) ($this->callback)($exception, $service);
        } catch (Throwable $e) {
            // If callback throws, don't trigger circuit breaker
            return false;
        }
    }

    public function getIdentifier(): string
    {
        return $this->identifier;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    /**
     * Create predicate from closure
     */
    public static function from(callable $callback, string $description = 'Custom callback'): self
    {
        return new self($callback, 'callback', $description);
    }

    /**
     * Create predicate that checks exception message
     */
    public static function messageContains(string $substring): self
    {
        return new self(
            callback: fn (Throwable $e, string $service) => str_contains($e->getMessage(), $substring),
            identifier: 'message_contains',
            description: "Message contains: '{$substring}'"
        );
    }

    /**
     * Create predicate that checks exception message with regex
     */
    public static function messageMatches(string $pattern): self
    {
        return new self(
            callback: fn (Throwable $e, string $service) => preg_match($pattern, $e->getMessage()) === 1,
            identifier: 'message_regex',
            description: "Message matches: {$pattern}"
        );
    }

    /**
     * Create predicate based on service name
     */
    public static function forService(string $targetService): self
    {
        return new self(
            callback: fn (Throwable $e, string $service) => $service === $targetService,
            identifier: 'service_specific',
            description: "Only for service: {$targetService}"
        );
    }
}
