<?php

declare(strict_types=1);

namespace App\Framework\CircuitBreaker\FailurePredicate;

use Throwable;

/**
 * Fluent builder for creating failure predicates
 */
final class FailurePredicateBuilder
{
    /**
     * @var array<class-string<Throwable>>
     */
    private array $includedExceptions = [];

    /**
     * @var array<class-string<Throwable>>
     */
    private array $excludedExceptions = [];

    /**
     * @var array<int>
     */
    private array $includedStatusCodes = [];

    /**
     * @var array<int>
     */
    private array $excludedStatusCodes = [];

    /**
     * @var array<FailurePredicate>
     */
    private array $additionalPredicates = [];

    private bool $includeServerErrors = false;

    private bool $includeClientErrors = false;

    private LogicalOperator $operator = LogicalOperator::AND;

    /**
     * Include specific exception types
     *
     * @param array<class-string<Throwable>> $exceptionTypes
     */
    public function includeExceptions(array $exceptionTypes): self
    {
        $this->includedExceptions = array_merge($this->includedExceptions, $exceptionTypes);

        return $this;
    }

    /**
     * Exclude specific exception types
     *
     * @param array<class-string<Throwable>> $exceptionTypes
     */
    public function excludeExceptions(array $exceptionTypes): self
    {
        $this->excludedExceptions = array_merge($this->excludedExceptions, $exceptionTypes);

        return $this;
    }

    /**
     * Include specific HTTP status codes
     *
     * @param array<int> $statusCodes
     */
    public function includeStatusCodes(array $statusCodes): self
    {
        $this->includedStatusCodes = array_merge($this->includedStatusCodes, $statusCodes);

        return $this;
    }

    /**
     * Exclude specific HTTP status codes
     *
     * @param array<int> $statusCodes
     */
    public function excludeStatusCodes(array $statusCodes): self
    {
        $this->excludedStatusCodes = array_merge($this->excludedStatusCodes, $statusCodes);

        return $this;
    }

    /**
     * Include server errors (5xx)
     */
    public function includeServerErrors(): self
    {
        $this->includeServerErrors = true;

        return $this;
    }

    /**
     * Include client errors (4xx)
     */
    public function includeClientErrors(): self
    {
        $this->includeClientErrors = true;

        return $this;
    }

    /**
     * Add custom predicate
     */
    public function addPredicate(FailurePredicate $predicate): self
    {
        $this->additionalPredicates[] = $predicate;

        return $this;
    }

    /**
     * Add callback predicate
     */
    public function addCallback(callable $callback, string $description = 'Custom callback'): self
    {
        $this->additionalPredicates[] = new CallbackFailurePredicate($callback, 'callback', $description);

        return $this;
    }

    /**
     * Set logical operator for combining predicates
     */
    public function withOperator(LogicalOperator $operator): self
    {
        $this->operator = $operator;

        return $this;
    }

    /**
     * Use AND operator (default)
     */
    public function and(): self
    {
        $this->operator = LogicalOperator::AND;

        return $this;
    }

    /**
     * Use OR operator
     */
    public function or(): self
    {
        $this->operator = LogicalOperator::OR;

        return $this;
    }

    /**
     * Build the failure predicate
     */
    public function build(): FailurePredicate
    {
        $predicates = [];

        // Add exception type predicate if needed
        if (! empty($this->includedExceptions) || ! empty($this->excludedExceptions)) {
            $predicates[] = new ExceptionTypeFailurePredicate(
                includedExceptions: $this->includedExceptions,
                excludedExceptions: $this->excludedExceptions,
                includeAllByDefault: empty($this->includedExceptions)
            );
        }

        // Add HTTP status predicate if needed
        if (! empty($this->includedStatusCodes) || ! empty($this->excludedStatusCodes) ||
            $this->includeServerErrors || $this->includeClientErrors) {
            $predicates[] = new HttpStatusFailurePredicate(
                includedStatusCodes: $this->includedStatusCodes,
                excludedStatusCodes: $this->excludedStatusCodes,
                includeServerErrors: $this->includeServerErrors,
                includeClientErrors: $this->includeClientErrors
            );
        }

        // Add additional predicates
        $predicates = array_merge($predicates, $this->additionalPredicates);

        // Return appropriate predicate
        if (empty($predicates)) {
            return ExceptionTypeFailurePredicate::all();
        }

        if (count($predicates) === 1) {
            return $predicates[0];
        }

        return new CompositeFailurePredicate($predicates, $this->operator);
    }
}
