<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core\Admin\CPT\Trait;

defined( 'ABSPATH' ) || exit;

/**
 * Trait for dispatching domain events from Custom Post Types
 *
 * This trait provides automatic event dispatching for CPT lifecycle events:
 * - Created: When a new post is saved for the first time
 * - Updated: When an existing post is modified
 * - Deleted: When a post is deleted
 *
 * Usage in CPT class:
 *
 * 1. Use this trait
 * 2. Implement abstract methods to define event classes
 * 3. Call registerEventHooks() in register() method
 *
 * Example:
 *
 * ```php
 * class AccommodationType extends AbstractCPT
 * {
 *     use DispatchesCPTEvents;
 *
 *     public function register(PluginInterface $plugin): void
 *     {
 *         parent::register($plugin);
 *         $this->registerEventHooks();
 *     }
 *
 *     protected function gatherPostData(int $postId): array
 *     {
 *         return [
 *             'name' => get_the_title($postId),
 *             'available_units' => carbon_get_post_meta($postId, 'available_units'),
 *         ];
 *     }
 *
 *     protected function createCreatedEvent(int $postId, array $data): object
 *     {
 *         return new AccommodationTypeWasCreated($postId, $data);
 *     }
 *
 *     protected function createUpdatedEvent(int $postId, array $data, array $changes): object
 *     {
 *         return new AccommodationTypeWasUpdated($postId, $data, $changes);
 *     }
 *
 *     protected function createDeletedEvent(int $postId, array $data): object
 *     {
 *         return new AccommodationTypeWasDeleted($postId, $data);
 *     }
 * }
 * ```
 */
trait DispatchesCPTEvents
{
    /**
     * Register WordPress hooks for event dispatching
     *
     * Should be called in CPT register() method after parent::register()
     */
    protected function registerEventHooks(): void
    {
        // After Carbon Fields saves all meta (priority 20 = after Carbon Fields saves at 10)
        $this->plugin->addAction(
            'carbon_fields_post_meta_container_saved',
            [$this, 'handleCPTSaveEvent'],
            20,
            1
        );

        // Before post deletion
        $this->plugin->addAction(
            'before_delete_post',
            [$this, 'handleCPTDeleteEvent'],
            10,
            2
        );
    }

    /**
     * Handle save event - dispatches created or updated event
     *
     * @internal Called via carbon_fields_post_meta_container_saved hook
     */
    public function handleCPTSaveEvent(int $postId): void
    {
        // Only for our post type
        if (get_post_type($postId) !== static::POST_TYPE) {
            return;
        }

        // Skip autosave, revisions
        if (wp_is_post_autosave($postId) || wp_is_post_revision($postId)) {
            return;
        }

        $post = get_post($postId);
        if (!$post) {
            return;
        }

        // Gather current data
        $currentData = $this->gatherPostData($postId);

        // Detect if this is create or update
        $isNewPost = $this->isNewPost($post);

        if ($isNewPost) {
            $this->dispatchCreatedEvent($postId, $currentData);
        } else {
            $this->dispatchUpdatedEvent($postId, $currentData);
        }
    }

    /**
     * Handle delete event
     *
     * @internal Called via before_delete_post hook
     */
    public function handleCPTDeleteEvent(int $postId, \WP_Post $post): void
    {
        // Only for our post type
        if ($post->post_type !== static::POST_TYPE) {
            return;
        }

        // Gather data before deletion
        $data = $this->gatherPostData($postId);

        $this->dispatchDeletedEvent($postId, $data);
    }

    /**
     * Dispatch created event
     */
    private function dispatchCreatedEvent(int $postId, array $data): void
    {
        $event = $this->createCreatedEvent($postId, $data);

        $this->plugin->getEventDispatcher()->dispatch($event);

        // Save snapshot for future change detection
        $this->saveDataSnapshot($postId, $data);
    }

    /**
     * Dispatch updated event
     */
    private function dispatchUpdatedEvent(int $postId, array $currentData): void
    {
        // Get previous data snapshot
        $previousData = $this->getDataSnapshot($postId);

        // Detect changes
        $changes = $this->detectChanges($previousData, $currentData);

        // Only dispatch if there are actual changes
        if (empty($changes)) {
            return;
        }

        $event = $this->createUpdatedEvent($postId, $currentData, $changes);

        $this->plugin->getEventDispatcher()->dispatch($event);

        // Update snapshot
        $this->saveDataSnapshot($postId, $currentData);
    }

    /**
     * Dispatch deleted event
     */
    private function dispatchDeletedEvent(int $postId, array $data): void
    {
        $event = $this->createDeletedEvent($postId, $data);

        $this->plugin->getEventDispatcher()->dispatch($event);
    }

    /**
     * Check if post is newly created
     *
     * Compares post_date and post_modified to determine if this is a new post
     */
    private function isNewPost(\WP_Post $post): bool
    {
        // If dates are equal (within 2 seconds), it's a new post
        $created = strtotime($post->post_date);
        $modified = strtotime($post->post_modified);

        return abs($created - $modified) < 2;
    }

    /**
     * Get previous data snapshot
     */
    private function getDataSnapshot(int $postId): array
    {
        $snapshot = get_post_meta($postId, '_bflex_cpt_data_snapshot', true);
        return is_array($snapshot) ? $snapshot : [];
    }

    /**
     * Save data snapshot for change detection
     */
    private function saveDataSnapshot(int $postId, array $data): void
    {
        update_post_meta($postId, '_bflex_cpt_data_snapshot', $data);
    }

    /**
     * Detect changes between previous and current data
     *
     * Default implementation compares all fields.
     * Override in CPT class for custom change detection logic.
     *
     * @param array $previous Previous post data
     * @param array $current Current post data
     * @return array Changes in format ['field' => ['old' => value, 'new' => value]]
     */
    protected function detectChanges(array $previous, array $current): array
    {
        $changes = [];

        foreach ($current as $key => $value) {
            if (!isset($previous[$key]) || $previous[$key] !== $value) {
                $changes[$key] = [
                    'old' => $previous[$key] ?? null,
                    'new' => $value
                ];
            }
        }

        return $changes;
    }

    // ===================================================================
    // Abstract methods - MUST be implemented in CPT class
    // ===================================================================

    /**
     * Gather all relevant data from post
     *
     * This method should collect all data that needs to be included
     * in events and used for change detection.
     *
     * Example:
     * ```php
     * protected function gatherPostData(int $postId): array
     * {
     *     $post = get_post($postId);
     *     return [
     *         'name' => $post->post_title,
     *         'status' => $post->post_status,
     *         'available_units' => (int)carbon_get_post_meta($postId, 'available_units'),
     *     ];
     * }
     * ```
     *
     * @param int $postId WordPress post ID
     * @return array Associative array of post data
     */
    abstract protected function gatherPostData(int $postId): array;

    /**
     * Create "Created" event instance
     *
     * Example:
     * ```php
     * protected function createCreatedEvent(int $postId, array $data): object
     * {
     *     return new AccommodationTypeWasCreated($postId, $data);
     * }
     * ```
     *
     * @param int $postId WordPress post ID
     * @param array $data Post data from gatherPostData()
     * @return object Event instance
     */
    abstract protected function createCreatedEvent(int $postId, array $data): object;

    /**
     * Create "Updated" event instance
     *
     * Example:
     * ```php
     * protected function createUpdatedEvent(int $postId, array $data, array $changes): object
     * {
     *     return new AccommodationTypeWasUpdated($postId, $data, $changes);
     * }
     * ```
     *
     * @param int $postId WordPress post ID
     * @param array $data Current post data
     * @param array $changes Detected changes
     * @return object Event instance
     */
    abstract protected function createUpdatedEvent(int $postId, array $data, array $changes): object;

    /**
     * Create "Deleted" event instance
     *
     * Example:
     * ```php
     * protected function createDeletedEvent(int $postId, array $data): object
     * {
     *     return new AccommodationTypeWasDeleted($postId, $data);
     * }
     * ```
     *
     * @param int $postId WordPress post ID
     * @param array $data Snapshot of post data before deletion
     * @return object Event instance
     */
    abstract protected function createDeletedEvent(int $postId, array $data): object;
}
