<?php
declare(strict_types=1);

namespace Ekesto\CacheWarmup;

use Ekesto\CacheWarmup\Debug;
use Ekesto\CacheWarmup\Multilingual\AdapterFactory;
use Ekesto\CacheWarmup\Multilingual\LanguageAdapterInterface;
use Ekesto\CacheWarmup\Settings\Repository;

/**
 * Priority-based URL reordering for cache warmup.
 *
 * Reorders sitemap URLs based on:
 * 1. Priority pages (warm first)
 * 2. Post type order (grouped by type)
 * 3. Other URLs (taxonomies, archives, etc.)
 */
final class PriorityFilter
{
    /** Limit URL samples in debug logs to keep support logs readable. */
    private const DEBUG_SAMPLE_LIMIT = 25;

    /**
     * Apply priority reordering to a flat array of URLs.
     *
     * @param array<int,string> $urls Flat array of URLs from sitemap
     * @return array<int,string> Reordered flat array of URLs
     */
    public static function applyPriorities(array $urls): array
    {
        $postTypeOrder = Repository::normalizePostTypeOrderArray(Repository::get('priorities.post_type_order'));
        $priorityPages = Repository::normalizePriorityPageIdsArray(Repository::get('priorities.priority_pages'));
        $languageAdapter = AdapterFactory::create();
        $defaultLanguage = $languageAdapter?->getDefaultLanguage();
        $defaultLanguage = is_string($defaultLanguage) ? strtolower(trim($defaultLanguage)) : '';

        return self::applyPrioritiesWithContext(
            $urls,
            $priorityPages,
            $postTypeOrder,
            $languageAdapter,
            $defaultLanguage,
            'PriorityFilter::applyPriorities'
        );
    }

    /**
     * Apply priority ordering using a captured planning snapshot.
     *
     * Snapshot-based preparation must not read live settings. Language ordering is
     * applied only when the current active adapter matches the captured adapter key.
     *
     * @param array<int,string> $urls Flat array of URLs from sitemap or fallback.
     * @param array<string,mixed> $snapshot Captured intent input snapshot.
     * @return array<int,string> Reordered flat array of URLs.
     */
    public static function applyPrioritiesFromSnapshot(array $urls, array $snapshot): array
    {
        $postTypeOrder = Repository::normalizePostTypeOrderArray($snapshot['priority_post_type_order'] ?? []);
        $priorityPages = Repository::normalizePriorityPageIdsArray($snapshot['priority_page_ids'] ?? []);
        $adapterKey = sanitize_key((string) ($snapshot['multilingual_adapter_key'] ?? ''));
        $defaultLanguage = strtolower(trim((string) ($snapshot['default_language'] ?? '')));

        $languageAdapter = null;
        $activeAdapter = AdapterFactory::create();
        if ($activeAdapter !== null && $adapterKey !== '' && $activeAdapter->getKey() === $adapterKey) {
            $languageAdapter = $activeAdapter;
        }

        return self::applyPrioritiesWithContext(
            $urls,
            $priorityPages,
            $postTypeOrder,
            $languageAdapter,
            $defaultLanguage,
            'PriorityFilter::applyPrioritiesFromSnapshot'
        );
    }

    /**
     * Apply priority ordering with explicit configuration inputs.
     *
     * @param array<int,string> $urls
     * @param array<int,int> $priorityPages
     * @param array<int,string> $postTypeOrder
     * @param LanguageAdapterInterface|null $languageAdapter
     * @param string $defaultLanguage
     * @param string $debugContext
     * @return array<int,string>
     */
    private static function applyPrioritiesWithContext(
        array $urls,
        array $priorityPages,
        array $postTypeOrder,
        ?LanguageAdapterInterface $languageAdapter,
        string $defaultLanguage,
        string $debugContext
    ): array {
        $hasPriorityConfig = !empty($postTypeOrder) || !empty($priorityPages);
        $hasLanguageOrdering = $languageAdapter !== null && $defaultLanguage !== '';

        if (!$hasPriorityConfig && !$hasLanguageOrdering) {
            return $urls;
        }

        $classified = self::classifyUrls($urls, $priorityPages, $postTypeOrder);

        if ($hasLanguageOrdering) {
            $urlLanguageCache = [];

            $classified['priority'] = self::orderBucketByLanguage(
                $classified['priority'],
                $languageAdapter,
                $defaultLanguage,
                $urlLanguageCache
            );

            foreach ($classified['by_type'] as $postType => $typeUrls) {
                $classified['by_type'][$postType] = self::orderBucketByLanguage(
                    $typeUrls,
                    $languageAdapter,
                    $defaultLanguage,
                    $urlLanguageCache
                );
            }

            $classified['other'] = self::orderBucketByLanguage(
                $classified['other'],
                $languageAdapter,
                $defaultLanguage,
                $urlLanguageCache
            );
        }

        $reordered = [];
        $reordered = array_merge($reordered, $classified['priority']);

        foreach ($postTypeOrder as $postType) {
            if (isset($classified['by_type'][$postType])) {
                $reordered = array_merge($reordered, $classified['by_type'][$postType]);
            }
        }

        $reordered = array_merge($reordered, $classified['other']);
        $finalUrls = array_values($reordered);

        $classificationDebug = [];
        if (isset($classified['debug']) && is_array($classified['debug'])) {
            $classificationDebug = $classified['debug'];
        }

        Debug::log($debugContext, [
            'total' => count($finalUrls),
            'priority_pages' => count($classified['priority']),
            'language_ordering' => $hasLanguageOrdering ? strtolower((string) $defaultLanguage) : false,
            'post_types' => array_map('count', array_intersect_key($classified['by_type'], array_flip($postTypeOrder))),
            'other' => count($classified['other']),
        ] + $classificationDebug);

        return $finalUrls;
    }

    /**
     * Classify URLs into priority pages, post type groups, and other.
     *
     * @param array<int,string> $urls
     * @param array<int,int> $priorityPageIds
     * @param array<int,string> $postTypeOrder
     * @return array<string,mixed>
     */
    private static function classifyUrls(array $urls, array $priorityPageIds, array $postTypeOrder): array
    {
        $priority = [];
        $byType = [];
        $other = [];
        $sampleLimit = self::DEBUG_SAMPLE_LIMIT;
        $unresolvedUrlsSample = [];
        $unresolvedUrlsCount = 0;
        $excludedResolvedUrlsSample = [];
        $excludedResolvedUrlsCount = 0;
        $excludedResolvedTypeCounts = [];

        foreach ($urls as $url) {
            // Try to resolve URL to post ID
            $postId = url_to_postid($url);
            if ($postId <= 0) {
                $other[] = $url;
                $unresolvedUrlsCount++;
                if ($unresolvedUrlsCount <= $sampleLimit) {
                    $unresolvedUrlsSample[] = $url;
                }
                continue;
            }

            // Priority pages
            if (in_array($postId, $priorityPageIds, true)) {
                $priority[] = $url;
                continue;
            }

            // Post type classification
            $postType = get_post_type($postId);
            if ($postType && in_array($postType, $postTypeOrder, true)) {
                if (!isset($byType[$postType])) {
                    $byType[$postType] = [];
                }
                $byType[$postType][] = $url;
                continue;
            }

            // Everything else (taxonomies, archives, unresolved URLs)
            $other[] = $url;

            $postTypeKey = is_string($postType) ? sanitize_key($postType) : '';
            $postTypeLabel = $postTypeKey !== '' ? $postTypeKey : 'unknown';

            if (!isset($excludedResolvedTypeCounts[$postTypeLabel])) {
                $excludedResolvedTypeCounts[$postTypeLabel] = 0;
            }
            $excludedResolvedTypeCounts[$postTypeLabel]++;

            $excludedResolvedUrlsCount++;
            if ($excludedResolvedUrlsCount <= $sampleLimit) {
                $excludedResolvedUrlsSample[] = [
                    'url' => $url,
                    'post_id' => $postId,
                    'post_type' => $postTypeLabel,
                ];
            }
        }

        ksort($excludedResolvedTypeCounts);
        $configuredPostTypeOrder = array_values(array_filter(
            array_map(
                static fn($type): string => is_string($type) ? sanitize_key($type) : '',
                $postTypeOrder
            ),
            static fn(string $type): bool => $type !== ''
        ));

        return [
            'priority' => $priority,
            'by_type' => $byType,
            'other' => $other,
            'debug' => [
                'configured_post_type_order' => $configuredPostTypeOrder,
                'unresolved_urls_count' => $unresolvedUrlsCount,
                'unresolved_urls_sample' => $unresolvedUrlsSample,
                'unresolved_urls_sample_truncated' => $unresolvedUrlsCount > $sampleLimit,
                'excluded_resolved_post_types' => $excludedResolvedTypeCounts,
                'excluded_resolved_urls_count' => $excludedResolvedUrlsCount,
                'excluded_resolved_urls_sample' => $excludedResolvedUrlsSample,
                'excluded_resolved_urls_sample_truncated' => $excludedResolvedUrlsCount > $sampleLimit,
            ],
        ];
    }

    /**
     * Reorder a URL bucket so default-language URLs come first.
     *
     * Stable partition order:
     * 1. Default language URLs
     * 2. Non-default language URLs
     * 3. Unknown-language URLs
     *
     * @param array<int,string> $urls
     * @param LanguageAdapterInterface $languageAdapter
     * @param string $defaultLanguage
     * @param array<string,string|null> $urlLanguageCache
     * @return array<int,string>
     */
    private static function orderBucketByLanguage(
        array $urls,
        LanguageAdapterInterface $languageAdapter,
        string $defaultLanguage,
        array &$urlLanguageCache
    ): array {
        $defaultLanguage = strtolower(trim($defaultLanguage));
        if ($defaultLanguage === '') {
            return $urls;
        }

        $defaultUrls = [];
        $nonDefaultUrls = [];
        $unknownUrls = [];

        foreach ($urls as $url) {
            $urlLanguage = self::resolveUrlLanguage($url, $languageAdapter, $urlLanguageCache);
            if ($urlLanguage === null || $urlLanguage === '') {
                $unknownUrls[] = $url;
                continue;
            }

            if (strtolower($urlLanguage) === $defaultLanguage) {
                $defaultUrls[] = $url;
            } else {
                $nonDefaultUrls[] = $url;
            }
        }

        return array_values(array_merge($defaultUrls, $nonDefaultUrls, $unknownUrls));
    }

    /**
     * Resolve a URL language using the active multilingual adapter.
     *
     * Unknown (null) is returned for unresolved or non-language-specific URLs.
     *
     * @param string $url
     * @param LanguageAdapterInterface $languageAdapter
     * @param array<string,string|null> $urlLanguageCache
     * @return string|null
     */
    private static function resolveUrlLanguage(
        string $url,
        LanguageAdapterInterface $languageAdapter,
        array &$urlLanguageCache
    ): ?string {
        if (array_key_exists($url, $urlLanguageCache)) {
            return $urlLanguageCache[$url];
        }

        $language = $languageAdapter->getUrlLanguage($url);
        if (!is_string($language) || trim($language) === '') {
            $urlLanguageCache[$url] = null;
            return null;
        }

        $language = strtolower(trim($language));
        $urlLanguageCache[$url] = $language;
        return $language;
    }

}
