<?php

declare(strict_types=1);

namespace App\Framework\ErrorBoundaries;

use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
use Throwable;

/**
 * Exception thrown when error boundary fails
 */
final class BoundaryFailedException extends FrameworkException
{
    public function __construct(
        string $message,
        private readonly string $boundaryName,
        private readonly ?Throwable $originalException = null,
        private readonly ?Throwable $fallbackException = null,
        int $code = 0,
        ?Throwable $previous = null
    ) {
        parent::__construct(
            message: $message,
            context: ExceptionContext::forOperation('error_boundary_failure', 'ErrorBoundary')
                ->withData([
                    'boundary_name' => $boundaryName,
                    'double_failure' => $originalException !== null && $fallbackException !== null,
                    'original_error' => $originalException?->getMessage(),
                    'fallback_error' => $fallbackException?->getMessage(),
                ]),
            code: $code,
            previous: $previous
        );
    }

    /**
     * Get the boundary name where failure occurred
     */
    public function getBoundaryName(): string
    {
        return $this->boundaryName;
    }

    /**
     * Get the original exception that caused the boundary to trigger
     */
    public function getOriginalException(): ?Throwable
    {
        return $this->originalException;
    }

    /**
     * Get the fallback exception (if fallback also failed)
     */
    public function getFallbackException(): ?Throwable
    {
        return $this->fallbackException;
    }

    /**
     * Check if this was a double failure (both operation and fallback failed)
     */
    public function isDoubleFailure(): bool
    {
        return $this->originalException !== null && $this->fallbackException !== null;
    }

    /**
     * Get detailed error information
     */
    public function getDetailedMessage(): string
    {
        $message = "Error boundary '{$this->boundaryName}' failed: {$this->getMessage()}";

        if ($this->originalException) {
            $message .= "\nOriginal error: " . $this->originalException->getMessage();
        }

        if ($this->fallbackException) {
            $message .= "\nFallback error: " . $this->fallbackException->getMessage();
        }

        return $message;
    }

    /**
     * Get all exceptions in the failure chain
     *
     * @return Throwable[]
     */
    public function getAllExceptions(): array
    {
        $exceptions = [$this];

        if ($this->originalException) {
            $exceptions[] = $this->originalException;
        }

        if ($this->fallbackException) {
            $exceptions[] = $this->fallbackException;
        }

        return $exceptions;
    }

    /**
     * Create exception for single failure
     */
    public static function singleFailure(string $boundaryName, Throwable $originalException): self
    {
        return new self(
            "Operation failed in boundary '{$boundaryName}'",
            $boundaryName,
            $originalException
        );
    }

    /**
     * Create exception for double failure (operation and fallback both failed)
     */
    public static function doubleFailure(
        string $boundaryName,
        Throwable $originalException,
        Throwable $fallbackException
    ): self {
        return new self(
            "Both operation and fallback failed in boundary '{$boundaryName}'",
            $boundaryName,
            $originalException,
            $fallbackException
        );
    }
}
