<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Resolvers;

use Ekesto\CacheInvalidator\Core\Config;

/**
 * Resolves page-target helper values shared by invalidation flows.
 */
final class PageTargetResolver
{
    /**
     * Check whether a mapping array contains wildcard token '*'.
     *
     * @param array<int,mixed> $values Mapping values.
     * @return bool
     */
    public static function containsWildcard(array $values): bool
    {
        foreach ($values as $value) {
            if (trim((string) $value) === '*') {
                return true;
            }
        }

        return false;
    }

    /**
     * Resolve all published page IDs.
     *
     * @return int[] Published page IDs.
     */
    public static function getAllPublishedPageIds(): array
    {
        $posts = get_posts([
            'post_type' => 'page',
            'post_status' => ['publish'],
            'posts_per_page' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        ]);

        if (!is_array($posts)) {
            return [];
        }

        return array_values(array_unique(array_filter(array_map('absint', $posts), static fn(int $id): bool => $id > 0)));
    }

    /**
     * Resolve published post IDs for the provided post types.
     *
     * @param array<int,mixed> $postTypes Post type keys.
     * @return int[] Published post IDs.
     */
    public static function getPublishedPostIdsByPostTypes(array $postTypes): array
    {
        $normalizedPostTypes = array_values(array_unique(array_filter(array_map(
            static fn($postType): string => sanitize_key((string) $postType),
            $postTypes
        ))));

        if (empty($normalizedPostTypes)) {
            return [];
        }

        $posts = get_posts([
            'post_type' => $normalizedPostTypes,
            'post_status' => ['publish'],
            'posts_per_page' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        ]);

        if (!is_array($posts)) {
            return [];
        }

        return array_values(array_unique(array_filter(array_map('absint', $posts), static fn(int $id): bool => $id > 0)));
    }

    /**
     * Resolve published post IDs for one post type using deterministic ID batching.
     *
     * Uses incremental ID windows (`ID > last_id`) instead of paged queries.
     *
     * @param string $postType Post type key.
     * @param int    $batchSize Batch size.
     * @return int[] Published post IDs.
     */
    public static function getPublishedPostIdsByPostTypeBatched(
        string $postType,
        int $batchSize = Config::FIRST_PUBLISH_SINGLE_FANOUT_BATCH_SIZE
    ): array {
        $postType = sanitize_key($postType);
        if ($postType === '') {
            return [];
        }

        $batchSize = max(1, $batchSize);
        $resolvedIds = [];
        $lastId = 0;

        global $wpdb;

        do {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
            $rows = $wpdb->get_col(
                $wpdb->prepare(
                    "SELECT ID
                     FROM {$wpdb->posts}
                     WHERE post_type = %s
                       AND post_status = 'publish'
                       AND ID > %d
                     ORDER BY ID ASC
                     LIMIT %d",
                    $postType,
                    $lastId,
                    $batchSize
                )
            );

            if (!is_array($rows) || empty($rows)) {
                break;
            }

            $batchIds = array_values(array_unique(array_filter(array_map('absint', $rows), static fn(int $id): bool => $id > 0)));
            if (empty($batchIds)) {
                break;
            }

            $resolvedIds = array_merge($resolvedIds, $batchIds);
            $lastId = (int) end($batchIds);

            if (count($batchIds) < $batchSize) {
                break;
            }
        } while (true);

        return array_values(array_unique(array_filter(array_map('absint', $resolvedIds), static fn(int $id): bool => $id > 0)));
    }
}
