<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Core;

use Ekesto\CacheInvalidator\ContentVisibility;
use Ekesto\CacheInvalidator\Integrations\Multilingual\FanoutPolicy;
use Ekesto\CacheInvalidator\Integrations\Multilingual\LanguageResolverInterface;

/**
 * Central trust classifier for emission URL subsets.
 *
 * Trust Model (ECI 4.x):
 * - Selected, structural, and edited-term archive origins are trusted.
 * - Other archive and filter origins remain untrusted.
 * - Trust must be explicitly set at emission time via trusted_urls.
 * - Absence of trusted_urls triggers fallback behavior (legacy compatibility).
 */
final class TrustDecider
{
    public const ORIGIN_SELECTED = 'selected';
    public const ORIGIN_EXPANDED_POSTS = 'expanded_posts';
    public const ORIGIN_TAXONOMY_ARCHIVES = 'taxonomy_archives';
    public const ORIGIN_EDITED_TERM_ARCHIVE = 'edited_term_archive';
    public const ORIGIN_POST_TYPE_ARCHIVES = 'post_type_archives';
    public const ORIGIN_RELATIONSHIP_PROPAGATION = 'relationship_propagation';
    public const ORIGIN_STRUCTURAL_TARGETS = 'structural_targets';

    /**
     * Determine whether URLs from one origin are trusted.
     *
     * @param string $origin Origin key.
     * @return bool
     */
    public static function classify(string $origin): bool
    {
        $origin = sanitize_key($origin);
        return $origin === self::ORIGIN_SELECTED
            || $origin === self::ORIGIN_STRUCTURAL_TARGETS
            || $origin === self::ORIGIN_EDITED_TERM_ARCHIVE;
    }

    /**
     * Merge origin URL buckets and keep only trusted origins.
     *
     * @param array<string,array<int,mixed>> $urlsByOrigin URL buckets keyed by origin.
     * @return array<int,string>
     */
    public static function trustedUrlsFromOriginUrls(array $urlsByOrigin): array
    {
        $trustedUrls = [];

        foreach ($urlsByOrigin as $origin => $urls) {
            if (!self::classify((string) $origin)) {
                continue;
            }

            if (!is_array($urls)) {
                continue;
            }

            $trustedUrls = array_merge($trustedUrls, self::normalizeUrls($urls));
        }

        return self::normalizeUrls($trustedUrls);
    }

    /**
     * Resolve trusted URLs from selected post IDs.
     *
     * @param array<int,mixed>              $postIds Selected target post IDs.
     * @param LanguageResolverInterface|null $resolver Active multilingual resolver.
     * @param array<int,mixed>              $fanoutLanguages Effective fanout languages.
     * @return array<int,string>
     */
    public static function resolveTrustedUrlsForSelectedPostIds(
        array $postIds,
        ?LanguageResolverInterface $resolver,
        array $fanoutLanguages = []
    ): array {
        $postIds = self::normalizePostIds($postIds);
        if ($postIds === []) {
            return [];
        }

        $urls = [];
        foreach ($postIds as $postId) {
            $post = get_post($postId);
            if (!$post instanceof \WP_Post) {
                continue;
            }

            $url = ContentVisibility::resolvePreciseUrlForPost($post);
            if ($url !== '') {
                $urls[] = $url;
            }
        }

        $urls = self::normalizeUrls($urls);
        if ($urls === [] || $resolver === null) {
            return $urls;
        }

        $fanoutLanguages = self::normalizeStringList($fanoutLanguages);
        if ($fanoutLanguages === []) {
            return $urls;
        }

        return self::normalizeUrls(FanoutPolicy::expandUrls($urls, $resolver, $fanoutLanguages));
    }

    /**
     * Normalize URL values.
     *
     * @param array<int,mixed> $urls Raw URL values.
     * @return array<int,string>
     */
    public static function normalizeUrls(array $urls): array
    {
        return array_values(array_unique(array_filter(array_map(
            static fn($url): string => trim((string) $url),
            $urls
        ))));
    }

    /**
     * Normalize post IDs.
     *
     * @param array<int,mixed> $postIds Raw IDs.
     * @return array<int,int>
     */
    public static function normalizePostIds(array $postIds): array
    {
        return array_values(array_unique(array_filter(array_map(
            'absint',
            $postIds
        ), static fn(int $id): bool => $id > 0)));
    }

    /**
     * Normalize a list of scalar string values.
     *
     * @param array<int,mixed> $values Raw values.
     * @return array<int,string>
     */
    public static function normalizeStringList(array $values): array
    {
        return array_values(array_unique(array_filter(array_map(
            static fn($value): string => trim((string) $value),
            $values
        ))));
    }

    /**
     * Intersect trusted URLs with one emitted URL set.
     *
     * @param array<int,mixed> $trustedUrls Candidate trusted URLs.
     * @param array<int,mixed> $allUrls Emitted URL set.
     * @return array<int,string>
     */
    public static function intersectWithUrls(array $trustedUrls, array $allUrls): array
    {
        $trustedUrls = self::normalizeUrls($trustedUrls);
        $allUrls = self::normalizeUrls($allUrls);
        if ($trustedUrls === [] || $allUrls === []) {
            return [];
        }

        $allLookup = array_fill_keys($allUrls, true);
        $intersected = [];
        foreach ($trustedUrls as $trustedUrl) {
            if (isset($allLookup[$trustedUrl])) {
                $intersected[] = $trustedUrl;
            }
        }

        return self::normalizeUrls($intersected);
    }
}
