<?php
/**
 * Action Result Value Object
 *
 * Immutable value object representing the result of an action execution.
 *
 * @package Sequensy
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\ValueObjects;

use Sequensy\Exceptions\SequensyException;
use Exception;
use WP_Error;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Class ActionResult
 *
 * Provides type-safe access to action execution results.
 */
class ActionResult {

	/**
	 * Whether the action succeeded.
	 *
	 * @var bool
	 */
	private bool $success;

	/**
	 * Result data (e.g., created post ID, API response).
	 *
	 * @var array
	 */
	private array $data;

	/**
	 * Error message if failed.
	 *
	 * @var string|null
	 */
	private ?string $error;

	/**
	 * Error code if failed.
	 *
	 * @var string|null
	 */
	private ?string $error_code;

	/**
	 * Whether the error is retryable.
	 *
	 * @var bool
	 */
	private bool $retryable;

	/**
	 * Private constructor - use success() or failure() factories.
	 *
	 * @param bool        $success    Whether successful.
	 * @param array       $data       Result data.
	 * @param string|null $error      Error message.
	 * @param string|null $error_code Error code.
	 * @param bool        $retryable  Whether retryable.
	 */
	private function __construct(
		bool $success,
		array $data = array(),
		?string $error = null,
		?string $error_code = null,
		bool $retryable = false
	) {
		$this->success    = $success;
		$this->data       = $data;
		$this->error      = $error;
		$this->error_code = $error_code;
		$this->retryable  = $retryable;
	}

	/**
	 * Create a successful result.
	 *
	 * @param array $data Result data.
	 * @return self Success result.
	 */
	public static function success( array $data = array() ): self {
		return new self( true, $data );
	}

	/**
	 * Create a failure result.
	 *
	 * @param string $error      Error message.
	 * @param string $error_code Error code.
	 * @param bool   $retryable  Whether the error is retryable.
	 * @param array  $data       Additional error data.
	 * @return self Failure result.
	 */
	public static function failure(
		string $error,
		string $error_code = 'action_failed',
		bool $retryable = false,
		array $data = array()
	): self {
		return new self( false, $data, $error, $error_code, $retryable );
	}

	/**
	 * Create from WP_Error.
	 *
	 * @param WP_Error $error     WP_Error object.
	 * @param bool     $retryable Whether the error is retryable.
	 * @return self Failure result.
	 */
	public static function from_wp_error( WP_Error $error, bool $retryable = false ): self {
		$error_data = $error->get_error_data();
		$data       = is_array( $error_data ) ? $error_data : array();

		return self::failure(
			$error->get_error_message(),
			$error->get_error_code(),
			$retryable,
			$data
		);
	}

	/**
	 * Create from exception.
	 *
	 * @param Exception $exception Exception object.
	 * @return self Failure result.
	 */
	public static function from_exception( Exception $exception ): self {
		$retryable = false;

		// Check for Sequensy Pro exceptions with retryable flag
		if ( $exception instanceof SequensyException && method_exists( $exception, 'is_retryable' ) ) {
			$retryable = $exception->is_retryable();
		}

		$data = array();
		if ( $exception instanceof SequensyException && method_exists( $exception, 'get_context' ) ) {
			$data = $exception->get_context();
		}

		return self::failure(
			$exception->getMessage(),
			'exception_' . $exception->getCode(),
			$retryable,
			$data
		);
	}

	/**
	 * Check if the action succeeded.
	 *
	 * @return bool True if successful.
	 */
	public function is_success(): bool {
		return $this->success;
	}

	/**
	 * Check if the action failed.
	 *
	 * @return bool True if failed.
	 */
	public function is_failure(): bool {
		return ! $this->success;
	}

	/**
	 * Get result data.
	 *
	 * @return array Result data.
	 */
	public function get_data(): array {
		return $this->data;
	}

	/**
	 * Get a specific data value.
	 *
	 * @param string $key           Data key.
	 * @param mixed  $default_value Default value.
	 * @return mixed Data value or default.
	 */
	public function get( string $key, mixed $default_value = null ): mixed {
		return $this->data[ $key ] ?? $default_value;
	}

	/**
	 * Get error message.
	 *
	 * @return string|null Error message or null if successful.
	 */
	public function get_error(): ?string {
		return $this->error;
	}

	/**
	 * Get error code.
	 *
	 * @return string|null Error code or null if successful.
	 */
	public function get_error_code(): ?string {
		return $this->error_code;
	}

	/**
	 * Check if the error is retryable.
	 *
	 * @return bool True if retryable.
	 */
	public function is_retryable(): bool {
		return $this->retryable;
	}

	/**
	 * Convert to array.
	 *
	 * @return array Result data.
	 */
	public function to_array(): array {
		$result = array(
			'success' => $this->success,
			'data'    => $this->data,
		);

		if ( ! $this->success ) {
			$result['error']      = $this->error;
			$result['error_code'] = $this->error_code;
			$result['retryable']  = $this->retryable;
		}

		return $result;
	}

	/**
	 * Convert to WP_Error if failed.
	 *
	 * @return WP_Error|null WP_Error if failed, null if successful.
	 */
	public function to_wp_error(): ?WP_Error {
		if ( $this->success ) {
			return null;
		}

		return new WP_Error(
			$this->error_code,
			$this->error,
			array_merge( $this->data, array( 'retryable' => $this->retryable ) )
		);
	}

	/**
	 * Merge with additional data.
	 *
	 * Returns a new result with merged data.
	 *
	 * @param array $additional_data Data to merge.
	 * @return self New result with merged data.
	 */
	public function with_data( array $additional_data ): self {
		return new self(
			$this->success,
			array_merge( $this->data, $additional_data ),
			$this->error,
			$this->error_code,
			$this->retryable
		);
	}
}
