<?php

declare(strict_types=1);

namespace App\Framework\Waf\ValueObjects;

/**
 * Additional contextual information for WAF detections and analysis
 * Flexible container for various types of context data
 */
final readonly class AdditionalContext
{
    public function __construct(
        public ?RequestContext $request = null,
        public ?SessionContext $session = null,
        public ?GeographicContext $geographic = null,
        public ?TechnicalContext $technical = null,
        public ?BusinessContext $business = null,
        public array $customData = []
    ) {
    }

    /**
     * Create empty context
     */
    public static function empty(): self
    {
        return new self();
    }

    /**
     * Create context with request information
     */
    public static function withRequest(RequestContext $request): self
    {
        return new self(request: $request);
    }

    /**
     * Create context with session information
     */
    public static function withSession(SessionContext $session): self
    {
        return new self(session: $session);
    }

    /**
     * Create context with geographic information
     */
    public static function withGeographic(GeographicContext $geographic): self
    {
        return new self(geographic: $geographic);
    }

    /**
     * Create context with custom data
     */
    public static function withCustomData(array $data): self
    {
        return new self(customData: $data);
    }

    /**
     * Check if context has request information
     */
    public function hasRequest(): bool
    {
        return $this->request !== null;
    }

    /**
     * Check if context has session information
     */
    public function hasSession(): bool
    {
        return $this->session !== null;
    }

    /**
     * Check if context has geographic information
     */
    public function hasGeographic(): bool
    {
        return $this->geographic !== null;
    }

    /**
     * Check if context has technical information
     */
    public function hasTechnical(): bool
    {
        return $this->technical !== null;
    }

    /**
     * Check if context has business information
     */
    public function hasBusiness(): bool
    {
        return $this->business !== null;
    }

    /**
     * Check if context has custom data
     */
    public function hasCustomData(): bool
    {
        return ! empty($this->customData);
    }

    /**
     * Merge with another context
     */
    public function merge(?self $other): self
    {
        if ($other === null) {
            return $this;
        }

        return new self(
            request: $other->request ?? $this->request,
            session: $other->session ?? $this->session,
            geographic: $other->geographic ?? $this->geographic,
            technical: $other->technical ?? $this->technical,
            business: $other->business ?? $this->business,
            customData: array_merge($this->customData, $other->customData)
        );
    }

    /**
     * Add request context
     */
    public function addRequest(RequestContext $request): self
    {
        return new self(
            request: $request,
            session: $this->session,
            geographic: $this->geographic,
            technical: $this->technical,
            business: $this->business,
            customData: $this->customData
        );
    }

    /**
     * Add session context
     */
    public function addSession(SessionContext $session): self
    {
        return new self(
            request: $this->request,
            session: $session,
            geographic: $this->geographic,
            technical: $this->technical,
            business: $this->business,
            customData: $this->customData
        );
    }

    /**
     * Add geographic context
     */
    public function addGeographic(GeographicContext $geographic): self
    {
        return new self(
            request: $this->request,
            session: $this->session,
            geographic: $geographic,
            technical: $this->technical,
            business: $this->business,
            customData: $this->customData
        );
    }

    /**
     * Add technical context
     */
    public function addTechnical(TechnicalContext $technical): self
    {
        return new self(
            request: $this->request,
            session: $this->session,
            geographic: $this->geographic,
            technical: $technical,
            business: $this->business,
            customData: $this->customData
        );
    }

    /**
     * Add business context
     */
    public function addBusiness(BusinessContext $business): self
    {
        return new self(
            request: $this->request,
            session: $this->session,
            geographic: $this->geographic,
            technical: $this->technical,
            business: $business,
            customData: $this->customData
        );
    }

    /**
     * Add custom data
     */
    public function addCustomData(array $data): self
    {
        return new self(
            request: $this->request,
            session: $this->session,
            geographic: $this->geographic,
            technical: $this->technical,
            business: $this->business,
            customData: array_merge($this->customData, $data)
        );
    }

    /**
     * Get custom value by key
     */
    public function getCustomValue(string $key, mixed $default = null): mixed
    {
        return $this->customData[$key] ?? $default;
    }

    /**
     * Check if custom key exists
     */
    public function hasCustomKey(string $key): bool
    {
        return array_key_exists($key, $this->customData);
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return array_filter([
            'request' => $this->request?->toArray(),
            'session' => $this->session?->toArray(),
            'geographic' => $this->geographic?->toArray(),
            'technical' => $this->technical?->toArray(),
            'business' => $this->business?->toArray(),
            'custom_data' => $this->customData,
        ], fn ($value) => $value !== null && $value !== []);
    }
}
