<?php
/**
 * Event Base Class.
 *
 * Base class for all events. System-wide events extend this class directly.
 * For user-specific events, use User_Event. For thread-specific events, use
 * Thread_Event.
 *
 * @package   Agenistrator
 * @since     1.0.0
 * @author    Alex Ruban <alex@maximum.guru>
 * @author    Maximum Software Inc <contact@maximum.software>
 * @license   GPL-3.0-only
 * @link      https://agenistrator.com/
 *
 * SPDX-FileCopyrightText: 2025-2026 Alex Ruban <alex@maximum.guru>
 * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc <contact@maximum.software>
 * SPDX-License-Identifier: GPL-3.0-only
 */

namespace Agenistrator;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Event class.
 */
abstract class Event {

	/**
	 * Registry mapping event types to their class names.
	 */
	private static $type_registry = array(
		'control'      => Events\Control_Event::class,
		'error'        => Events\Thread_Error_Event::class,
		'message'      => Events\Message_Event::class,
		'progress'     => Events\Progress_Event::class,
		'stream_chunk' => Events\Stream_Chunk_Event::class,
	);

	/**
	 * Event ID
	 *
	 * @var int
	 * @since     1.0.0
	 */
	protected $id;

	/**
	 * Event creation timestamp
	 *
	 * @var string
	 * @since     1.0.0
	 */
	protected $created_at;

	/**
	 * Constructor.
	 * @since     1.0.0
	 */
	public function __construct() {
		$this->created_at = current_time( 'mysql', true );
	}

	/**
	 * Get the event type.
	 *
	 * @return string
	 * @since     1.0.0
	 */
	abstract public function get_type();

	/**
	 * Get the event data.
	 *
	 * @return array
	 * @since     1.0.0
	 */
	abstract public function get_data();

	/**
	 * Get the event ID.
	 *
	 * @return int
	 * @since     1.0.0
	 */
	public function get_id() {
		return $this->id;
	}

	/**
	 * Set the event ID.
	 *
	 * @param int $id The event ID.
	 * @return $this
	 * @since     1.0.0
	 */
	public function set_id( $id ) {
		$this->id = $id;
		return $this;
	}

	/**
	 * Get the creation timestamp.
	 *
	 * @return string
	 * @since     1.0.0
	 */
	public function get_created_at() {
		return $this->created_at;
	}

	/**
	 * Push the event to the queue.
	 *
	 * @return int|false The ID of the inserted event or false on failure.
	 * @since     1.0.0
	 */
	public function push() {
		$id = Event_Queue::get_instance()->push_event( $this );
		if ( $id ) {
			$this->set_id( $id );
		}
		return $id;
	}

	/**
	 * Create an event from array data.
	 *
	 * When called on the base Event class, this acts as a factory that
	 * dispatches to the appropriate subclass based on the event_type.
	 * Subclasses override this to handle their specific data.
	 *
	 * @param array $data The event data including 'event_type'.
	 * @return static|null The event instance or null if type is unknown.
	 * @since     1.0.0
	 */
	public static function from_array( $data ) {
		// If called on a subclass, let the subclass handle it
		if ( static::class !== self::class ) {
			$event = new static();
			if ( isset( $data['id'] ) ) {
				$event->set_id( $data['id'] );
			}
			return $event;
		}

		// Factory: dispatch to the appropriate subclass
		$event_type = $data['event_type'] ?? null;
		if ( null === $event_type || ! isset( self::$type_registry[ $event_type ] ) ) {
			return null;
		}

		$class = self::$type_registry[ $event_type ];
		return $class::from_array( $data );
	}

	/**
	 * Convert the event to array.
	 *
	 * @return array
	 * @since     1.0.0
	 */
	public function to_array() {
		return array(
			'id'         => $this->get_id(),
			'event_type' => $this->get_type(),
			'data'       => $this->get_data(),
			'created_at' => $this->get_created_at(),
		);
	}

	/**
	 * Get the SSE payload for this event.
	 *
	 * Returns the data that should be sent to the client via SSE.
	 * Concrete event classes must override to expose only what the frontend needs.
	 *
	 * @return array
	 * @since     1.0.0
	 */
	public function to_sse_payload() {
		return array(
			'id'         => $this->get_id(),
			'event_type' => $this->get_type(),
			'created_at' => $this->get_created_at(),
		);
	}

	/**
	 * Convert event to JSON string.
	 *
	 * @return string JSON representation
	 * @since     1.0.0
	 */
	public function to_json() {
		return wp_json_encode( $this->to_array() );
	}

	/**
	 * Create event instance from JSON string.
	 *
	 * @param string $json JSON string
	 * @return static|null Event instance or null if invalid
	 * @since     1.0.0
	 */
	public static function from_json( $json ) {
		$data = json_decode( $json, true );
		if ( ! is_array( $data ) ) {
			return null;
		}
		return static::from_array( $data );
	}
}
