<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Listeners;

use Ekesto\CacheInvalidator\Integrations\Caching\AdapterManager;
use Ekesto\CacheInvalidator\Core\EmissionBuilder;
use Ekesto\CacheInvalidator\Core\EmissionHelper;
use Ekesto\CacheInvalidator\Core\InvalidationEngine;
use Ekesto\CacheInvalidator\InvalidatorRuntime;
use Ekesto\CacheInvalidator\Core\PurgeCoordinator;
use Ekesto\CacheInvalidator\Core\TrustDecider;
use Ekesto\CacheInvalidator\Integrations\Multilingual\LanguageResolverFactory;
use Ekesto\CacheInvalidator\Resolvers\RelationshipGraphResolver;
use Ekesto\CacheInvalidator\Support\Debug;

/**
 * Listens to comment lifecycle events and invalidates affected targets.
 */
final class CommentInvalidationListener
{
    /** @var array<int,true> Request-local dedupe for comment IDs. */
    private static array $processedComments = [];

    /**
     * Register comment hooks.
     *
     * @return void
     */
    public static function registerHooks(): void
    {
        add_action('wp_insert_comment', [self::class, 'handleInsertComment'], 25, 2);
        add_action('edit_comment', [self::class, 'handleEditComment'], 25, 2);
        add_action('transition_comment_status', [self::class, 'handleTransitionCommentStatus'], 25, 3);
        add_action('delete_comment', [self::class, 'handleDeleteComment'], 25, 2);
    }

    /**
     * Handle comment insertion.
     *
     * @param int         $commentId
     * @param \WP_Comment $comment
     * @return void
     */
    public static function handleInsertComment(int $commentId, \WP_Comment $comment): void
    {
        unset($comment);
        self::invalidateForComment($commentId, 'comment_insert');
    }

    /**
     * Handle comment edits.
     *
     * @param int                 $commentId
     * @param array<string,mixed> $data
     * @return void
     */
    public static function handleEditComment(int $commentId, array $data = []): void
    {
        unset($data);
        self::invalidateForComment($commentId, 'comment_edit');
    }

    /**
     * Handle comment moderation transitions.
     *
     * @param string      $newStatus
     * @param string      $oldStatus
     * @param \WP_Comment $comment
     * @return void
     */
    public static function handleTransitionCommentStatus(string $newStatus, string $oldStatus, \WP_Comment $comment): void
    {
        if (!self::isApprovedStatus($newStatus) && !self::isApprovedStatus($oldStatus)) {
            return;
        }

        self::invalidateForComment((int) $comment->comment_ID, 'comment_status_change');
    }

    /**
     * Handle comment deletion.
     *
     * @param int         $commentId
     * @param \WP_Comment $comment
     * @return void
     */
    public static function handleDeleteComment(int $commentId, \WP_Comment $comment): void
    {
        unset($comment);
        self::invalidateForComment($commentId, 'comment_delete');
    }

    /**
     * Run configured post-type invalidation for one comment event.
     *
     * @param int    $commentId
     * @param string $event
     * @return void
     */
    private static function invalidateForComment(int $commentId, string $event): void
    {
        if (!InvalidatorRuntime::settingsRepository()->isInvalidationEnabled()) {
            return;
        }

        $global = InvalidatorRuntime::settingsRepository()->getGlobalInvalidation();
        if (empty($global['comments_enabled'])) {
            return;
        }

        if ($commentId <= 0 || isset(self::$processedComments[$commentId])) {
            return;
        }

        $comment = get_comment($commentId);
        if (!$comment instanceof \WP_Comment) {
            return;
        }

        $postId = (int) $comment->comment_post_ID;
        if ($postId <= 0 || InvalidationEngine::wasProcessedInRequest($postId)) {
            return;
        }

        $post = get_post($postId);
        if (!$post instanceof \WP_Post || $post->post_status !== 'publish') {
            return;
        }

        $settingsRepository = InvalidatorRuntime::settingsRepository();
        $postType = sanitize_key((string) $post->post_type);
        if ($postType === '') {
            return;
        }

        $extraUrls = [];
        $postTypeObject = get_post_type_object((string) $post->post_type);
        if ($postTypeObject instanceof \WP_Post_Type && $postTypeObject->publicly_queryable) {
            $permalink = (string) get_permalink($postId);
            if ($permalink !== '') {
                $extraUrls[] = $permalink;
            }
        }

        $candidateConfiguredPostTypes = array_values(array_unique(array_merge(
            [$postType],
            RelationshipGraphResolver::resolveAffectedHosts($postType)
        )));

        $activeConfiguredPostTypes = [];
        $selectedTargetPageIds = [];
        $targetPageIds = [];

        foreach ($candidateConfiguredPostTypes as $configuredPostType) {
            $postTypeConfig = $settingsRepository->getPostTypeConfig((string) $configuredPostType);
            if ($postTypeConfig === null || !($postTypeConfig['enabled'] ?? true)) {
                continue;
            }

            $activeConfiguredPostTypes[] = (string) $configuredPostType;

            $targetMode = $settingsRepository->getPostTypeTargetMode((string) $configuredPostType);
            if ($targetMode === 'selected') {
                $configuredSelectedPageIds = $settingsRepository->getPostTypeTargetPageIds((string) $configuredPostType);
                $selectedTargetPageIds = array_merge($selectedTargetPageIds, $configuredSelectedPageIds);
                $targetPageIds = array_merge($targetPageIds, $configuredSelectedPageIds);
                continue;
            }

            if ($targetMode === 'all') {
                $targetPageIds = array_merge(
                    $targetPageIds,
                    $settingsRepository->getEffectivePostTypeTargetPageIds((string) $configuredPostType)
                );
            }
        }

        $activeConfiguredPostTypes = array_values(array_unique(array_filter($activeConfiguredPostTypes)));
        if (empty($activeConfiguredPostTypes) && empty($extraUrls)) {
            return;
        }

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

        if (empty($targetPageIds) && empty($extraUrls)) {
            return;
        }

        $adapter = AdapterManager::getActiveAdapter();
        if ($adapter === null) {
            return;
        }

        $resolver = LanguageResolverFactory::create();
        $triggerLanguage = $resolver ? $resolver->getTriggerLanguage($postId) : null;
        $result = PurgeCoordinator::purgeTargets(
            $targetPageIds,
            $extraUrls,
            $adapter,
            $resolver,
            $triggerLanguage,
            [
                'trigger_class' => 'comment',
                'trigger_kind' => 'comment',
                'trigger_post_id' => $postId,
                'trigger_post_type' => (string) $post->post_type,
                'trigger_event' => $event,
                'reason' => 'comment_change',
            ]
        );
        $emittedUrls = $result['emitted_urls'] ?? [];
        if (empty($emittedUrls)) {
            return;
        }
        $fanoutLanguages = TrustDecider::normalizeStringList((array) ($result['fanout_languages'] ?? []));
        $trustedUrls = TrustDecider::trustedUrlsFromOriginUrls([
            TrustDecider::ORIGIN_SELECTED => TrustDecider::resolveTrustedUrlsForSelectedPostIds(
                $selectedTargetPageIds,
                $resolver,
                $fanoutLanguages
            ),
        ]);
        $trustedUrls = TrustDecider::intersectWithUrls($trustedUrls, $emittedUrls);
        Debug::verbose('[ECI] trust classification', [
            'trusted' => count($trustedUrls),
            'origin' => 'selected',
        ]);
        if (!empty($extraUrls)) {
            Debug::verbose('[ECI] untrusted origin', [
                'origin' => 'derived',
                'count' => count($extraUrls),
            ]);
        }

        self::$processedComments[$commentId] = true;

        $context = [
            'source' => $adapter->getKey(),
            'source_label' => $adapter->getLabel(),
            'reason' => 'comment_change',
            'trigger_class' => 'comment',
            'trigger_event' => $event,
            'trigger_post_id' => $postId,
            'trigger_post_type' => (string) $post->post_type,
            'trigger_comment_id' => $commentId,
            'groups' => $activeConfiguredPostTypes,
            'purged_urls_directly' => $result['purged_urls_directly'] ?? [],
            'purge_skipped_urls' => $result['purge_skipped_urls'] ?? [],
            'fanout_mode' => $result['fanout_mode'] ?? 'same_language',
            'fanout_languages' => $fanoutLanguages,
            'synchronous_targeted_resolution' => true,
            'trusted_urls' => $trustedUrls,
        ];
        $emissionPayload = EmissionBuilder::build((array) ($context['trusted_urls'] ?? []), $emittedUrls);
        $context['trusted_urls'] = $emissionPayload['trusted_urls'];

        EmissionHelper::emit($emissionPayload['urls'], $context);
    }

    /**
     * Check whether a comment status means approved/published.
     *
     * @param string $status
     * @return bool
     */
    private static function isApprovedStatus(string $status): bool
    {
        return in_array($status, ['1', 'approved'], true);
    }
}
