<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Core;

use Ekesto\CacheInvalidator\ContentVisibility;
use Ekesto\CacheInvalidator\Core\Elementor\ElementorDocumentClassification;
use Ekesto\CacheInvalidator\Support\Debug;

/**
 * Shadow-only lifecycle diagnostics for runtime ownership tracing.
 */
final class LifecycleProbe
{
    /** @var int Request-local probe sequence counter. */
    private static int $sequence = 0;

    /**
     * Operationally useful probes kept enabled after lifecycle migration cleanup.
     *
     * All other probe events remain callable but silent by default.
     *
     * @var array<string,bool> Enabled probe events.
     */
    private const ENABLED_EVENTS = [
        'dedupe_decision' => true,
        'elementor_conditions_meta_dedupe' => true,
        'elementor_conditions_meta_seen' => true,
        'elementor_presentation_suppression' => true,
    ];

    /**
     * Register passive lifecycle probe hooks.
     *
     * @return void
     */
    public static function registerHooks(): void
    {
        add_action('wp_after_insert_post', [self::class, 'onWpAfterInsertPost'], 10, 4);
    }

    /**
     * Probe `wp_after_insert_post` without influencing lifecycle behavior.
     *
     * @param int      $postId Post ID.
     * @param \WP_Post $post Current post object.
     * @param bool     $update Whether this is an update.
     * @param \WP_Post|null $postBefore Previous post object.
     * @return void
     */
    public static function onWpAfterInsertPost(int $postId, \WP_Post $post, bool $update, $postBefore): void
    {
        $previous = $postBefore instanceof \WP_Post ? $postBefore : null;
        self::log('wp_after_insert_post', $post, [
            'hook_name' => 'wp_after_insert_post',
            'hook_priority' => 10,
            'old_status' => $previous instanceof \WP_Post ? (string) $previous->post_status : '',
            'new_status' => (string) $post->post_status,
            'lifecycle_classifier_result' => ContentLifecycle::classifyTransition($previous, $post, [
                'update' => $update,
                'source_hook' => 'wp_after_insert_post',
            ]),
            'elementor_state' => self::elementorState($post),
            'dedupe_decisions' => [],
            'invalidation_emitted' => false,
            'structural_claimed' => false,
            'generic_lifecycle_must_stop' => false,
        ]);
    }

    /**
     * Log one structured probe event.
     *
     * @param string               $event Event key.
     * @param \WP_Post|null        $post Post object.
     * @param array<string,mixed>  $context Diagnostic context.
     * @return void
     */
    public static function log(string $event, ?\WP_Post $post, array $context = []): void
    {
        if (!Debug::enabled()) {
            return;
        }

        if (empty(self::ENABLED_EVENTS[$event])) {
            return;
        }

        $base = self::baseContext($post, $context);
        Debug::log('[PROBE] ' . sanitize_key($event), array_merge($base, $context));
    }

    /**
     * Build default probe fields required for trace reconstruction.
     *
     * @param \WP_Post|null       $post Post object.
     * @param array<string,mixed> $context Existing context.
     * @return array<string,mixed>
     */
    private static function baseContext(?\WP_Post $post, array $context): array
    {
        $postType = $post instanceof \WP_Post ? sanitize_key((string) $post->post_type) : '';

        return [
            'correlation_id' => EmissionHelper::getOrCreateRequestCorrelationId(),
            'probe_sequence' => ++self::$sequence,
            'hook_name' => (string) ($context['hook_name'] ?? current_filter()),
            'hook_priority' => $context['hook_priority'] ?? self::currentPriority((string) ($context['hook_name'] ?? current_filter())),
            'post_id' => $post instanceof \WP_Post ? (int) $post->ID : 0,
            'post_type' => $postType,
            'old_status' => (string) ($context['old_status'] ?? ''),
            'new_status' => (string) ($context['new_status'] ?? ($post instanceof \WP_Post ? (string) $post->post_status : '')),
            'builder_classification' => $post instanceof \WP_Post ? self::builderClassification($post) : [],
            'structural_classification_result' => $context['structural_classification_result'] ?? self::structuralClassification($post),
            'lifecycle_classifier_result' => $context['lifecycle_classifier_result'] ?? null,
            'dedupe_decisions' => $context['dedupe_decisions'] ?? [],
            'invalidation_emitted' => (bool) ($context['invalidation_emitted'] ?? false),
            'structural_claimed' => (bool) ($context['structural_claimed'] ?? false),
            'generic_lifecycle_must_stop' => (bool) ($context['generic_lifecycle_must_stop'] ?? false),
        ];
    }

    /**
     * Return current hook priority when WordPress exposes it.
     *
     * @param string $hook Hook name.
     * @return int|null Current priority.
     */
    private static function currentPriority(string $hook): ?int
    {
        if ($hook === '' || !function_exists('has_action')) {
            return null;
        }

        global $wp_filter;
        if (!isset($wp_filter[$hook]) || !is_object($wp_filter[$hook]) || !method_exists($wp_filter[$hook], 'current_priority')) {
            return null;
        }

        $priority = $wp_filter[$hook]->current_priority();
        return is_int($priority) ? $priority : null;
    }

    /**
     * Build builder scan classification diagnostics.
     *
     * @param \WP_Post $post Post object.
     * @return array<string,mixed>
     */
    private static function builderClassification(\WP_Post $post): array
    {
        $postId = (int) $post->ID;
        if (
            metadata_exists('post', $postId, '_elementor_data')
            || metadata_exists('post', $postId, '_elementor_edit_mode')
            || in_array(sanitize_key((string) $post->post_type), ['elementor_component', 'elementor_library'], true)
        ) {
            return [
                'builder' => 'elementor',
                'confidence' => 'probe_meta',
                'signals' => ['_elementor_data', '_elementor_edit_mode', 'post_type'],
            ];
        }

        return [
            'builder' => 'core',
            'confidence' => 'probe_default',
            'signals' => [],
        ];
    }

    /**
     * Build broad structural classification diagnostics.
     *
     * @param \WP_Post|null $post Post object.
     * @return array<string,mixed>
     */
    private static function structuralClassification(?\WP_Post $post): array
    {
        if (!$post instanceof \WP_Post) {
            return [
                'is_structural_post_type' => false,
                'elementor_document_classifier' => '',
            ];
        }

        $postType = sanitize_key((string) $post->post_type);

        return [
            'is_structural_post_type' => ContentVisibility::isStructuralPostType($postType),
            'elementor_document_classifier' => $postType === 'elementor_library'
                ? ContentVisibility::classifyElementorLibraryPost($post)
                : '',
        ];
    }

    /**
     * Return Elementor timing state for one post.
     *
     * @param \WP_Post $post Post object.
     * @param object|null $document Optional Elementor document object.
     * @return array<string,mixed>
     */
    public static function elementorState(\WP_Post $post, ?object $document = null): array
    {
        $postId = (int) $post->ID;
        $document = $document ?? self::resolveElementorDocument($postId);

        return [
            '_elementor_data' => self::summarizeMeta(get_post_meta($postId, '_elementor_data', true)),
            '_elementor_template_type' => get_post_meta($postId, '_elementor_template_type', true),
            '_elementor_conditions' => self::summarizeMeta(get_post_meta($postId, '_elementor_conditions', true)),
            '_elementor_edit_mode' => get_post_meta($postId, '_elementor_edit_mode', true),
            'document_class' => is_object($document) ? get_class($document) : '',
            'document_name' => is_object($document) && method_exists($document, 'get_name') ? (string) $document->get_name() : '',
            'document_classifier_result' => sanitize_key((string) $post->post_type) === 'elementor_library'
                ? ContentVisibility::classifyElementorLibraryPost($post)
                : (sanitize_key((string) $post->post_type) === 'elementor_component' ? ElementorDocumentClassification::STRUCTURAL_CONTAINER : ''),
        ];
    }

    /**
     * Resolve an Elementor runtime document when available.
     *
     * @param int $postId Post ID.
     * @return object|null Elementor document.
     */
    private static function resolveElementorDocument(int $postId): ?object
    {
        if (!class_exists('\Elementor\Plugin')) {
            return null;
        }

        $plugin = \Elementor\Plugin::$instance ?? null;
        if (!is_object($plugin) || !isset($plugin->documents) || !is_object($plugin->documents) || !method_exists($plugin->documents, 'get')) {
            return null;
        }

        $document = $plugin->documents->get($postId);
        return is_object($document) ? $document : null;
    }

    /**
     * Summarize potentially large meta payloads without mutating them.
     *
     * @param mixed $value Raw meta value.
     * @return array<string,mixed>
     */
    private static function summarizeMeta($value): array
    {
        if (is_string($value)) {
            return [
                'type' => 'string',
                'present' => trim($value) !== '',
                'length' => strlen($value),
                'hash' => md5($value),
            ];
        }

        if (is_array($value)) {
            return [
                'type' => 'array',
                'present' => !empty($value),
                'count' => count($value),
                'hash' => md5((string) wp_json_encode($value)),
            ];
        }

        return [
            'type' => gettype($value),
            'present' => !empty($value),
            'hash' => md5((string) wp_json_encode($value)),
        ];
    }
}
