<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator;

/**
 * Value object carrying normalized resolver output for reference-first invalidation.
 */
final class UsageResolutionResult
{
    /**
     * @var int[]
     */
    private array $postIds;

    private bool $hasReferences;

    private bool $uncertainDueToContext;

    private bool $uncertainDueToScanGap;

    private int $directMatchesCount;

    private int $structuralMatchesCount;

    /**
     * @var string[]
     */
    private array $targetUrls;

    private int $directUrlMatchesCount;

    private string $fallbackReasonHint;

    /**
     * Build a result object.
     *
     * @param int[] $postIds Direct precise target IDs.
     * @param bool  $hasReferences Whether any direct or structural references were found.
     * @param bool  $uncertainDueToContext Whether context prevents deterministic full targeting.
     * @param bool  $uncertainDueToScanGap Whether supported scan coverage is incomplete.
     * @param int   $directMatchesCount Unique direct match count.
     * @param int   $structuralMatchesCount Unique structural match count.
     * @param string[] $targetUrls Direct URL targets.
     * @param int   $directUrlMatchesCount Unique direct URL match count.
     * @param string $fallbackReasonHint Diagnostic fallback reason hint.
     */
    public function __construct(
        array $postIds,
        bool $hasReferences,
        bool $uncertainDueToContext,
        bool $uncertainDueToScanGap,
        int $directMatchesCount,
        int $structuralMatchesCount,
        array $targetUrls = [],
        int $directUrlMatchesCount = 0,
        string $fallbackReasonHint = ''
    ) {
        $this->postIds = ContentVisibility::normalizePostIds($postIds);
        $this->targetUrls = ContentVisibility::normalizeUrls($targetUrls);
        $this->directMatchesCount = max(count($this->postIds), max(0, $directMatchesCount));
        $this->directUrlMatchesCount = max(count($this->targetUrls), max(0, $directUrlMatchesCount));
        $this->structuralMatchesCount = max(0, $structuralMatchesCount);
        $this->fallbackReasonHint = sanitize_key($fallbackReasonHint);
        $this->hasReferences = $hasReferences
            || $this->directMatchesCount > 0
            || $this->directUrlMatchesCount > 0
            || $this->structuralMatchesCount > 0;
        $this->uncertainDueToContext = $uncertainDueToContext;
        $this->uncertainDueToScanGap = $uncertainDueToScanGap;
    }

    /**
     * Return precise target IDs.
     *
     * @return int[]
     */
    public function postIds(): array
    {
        return $this->postIds;
    }

    /**
     * Return precise target URLs.
     *
     * @return string[]
     */
    public function targetUrls(): array
    {
        return $this->targetUrls;
    }

    /**
     * Return whether any references were detected.
     *
     * @return bool
     */
    public function hasReferences(): bool
    {
        return $this->hasReferences;
    }

    /**
     * Return whether the resolution is uncertain.
     *
     * @return bool
     */
    public function uncertain(): bool
    {
        return $this->uncertainDueToContext || $this->uncertainDueToScanGap;
    }

    /**
     * Return context uncertainty.
     *
     * @return bool
     */
    public function uncertainDueToContext(): bool
    {
        return $this->uncertainDueToContext;
    }

    /**
     * Return scan-gap uncertainty.
     *
     * @return bool
     */
    public function uncertainDueToScanGap(): bool
    {
        return $this->uncertainDueToScanGap;
    }

    /**
     * Return direct match count.
     *
     * @return int
     */
    public function directMatchesCount(): int
    {
        return $this->directMatchesCount;
    }

    /**
     * Return structural match count.
     *
     * @return int
     */
    public function structuralMatchesCount(): int
    {
        return $this->structuralMatchesCount;
    }

    /**
     * Return direct URL match count.
     *
     * @return int
     */
    public function directUrlMatchesCount(): int
    {
        return $this->directUrlMatchesCount;
    }

    /**
     * Return diagnostic fallback reason hint.
     *
     * @return string
     */
    public function fallbackReasonHint(): string
    {
        return $this->fallbackReasonHint;
    }

    /**
     * Return normalized array payload.
     *
     * @return array{post_ids:int[],target_urls:string[],has_references:bool,uncertain:bool,uncertain_due_to_context:bool,uncertain_due_to_scan_gap:bool,direct_matches_count:int,direct_url_matches_count:int,structural_matches_count:int,fallback_reason_hint:string}
     */
    public function toArray(): array
    {
        return [
            'post_ids' => $this->postIds(),
            'target_urls' => $this->targetUrls(),
            'has_references' => $this->hasReferences(),
            'uncertain' => $this->uncertain(),
            'uncertain_due_to_context' => $this->uncertainDueToContext(),
            'uncertain_due_to_scan_gap' => $this->uncertainDueToScanGap(),
            'direct_matches_count' => $this->directMatchesCount(),
            'direct_url_matches_count' => $this->directUrlMatchesCount(),
            'structural_matches_count' => $this->structuralMatchesCount(),
            'fallback_reason_hint' => $this->fallbackReasonHint(),
        ];
    }
}
