<?php
/**
 * Workflow Config Value Object
 *
 * Immutable value object representing a workflow's configuration.
 *
 * @package Sequensy
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\ValueObjects;

use WP_Error;

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

/**
 * Class WorkflowConfig
 *
 * Provides type-safe access to workflow configuration data.
 */
class WorkflowConfig implements \JsonSerializable {

	/**
	 * Trigger configuration.
	 *
	 * @var TriggerConfig
	 */
	private TriggerConfig $trigger;

	/**
	 * Actions for this workflow, keyed by action ID.
	 *
	 * @var array<string, ActionConfig>
	 */
	private array $actions;

	/**
	 * Entry action ID (first action to execute).
	 *
	 * @var string|null
	 */
	private ?string $entry_action;

	/**
	 * Layout configuration.
	 *
	 * @var array|null
	 */
	private ?array $layout;

	/**
	 * Private constructor - use from_array() or from_json().
	 *
	 * @param TriggerConfig               $trigger      Trigger configuration.
	 * @param array<string, ActionConfig> $actions      Action configurations keyed by ID.
	 * @param string|null                 $entry_action Entry action ID.
	 * @param array|null                  $layout       Layout configuration.
	 */
	private function __construct(
		TriggerConfig $trigger,
		array $actions,
		?string $entry_action = null,
		?array $layout = null
	) {
		$this->trigger      = $trigger;
		$this->actions      = $actions;
		$this->entry_action = $entry_action;
		$this->layout       = $layout;
	}

	/**
	 * Create from array data.
	 *
	 * @param array $data Configuration data.
	 * @return self|WP_Error Config object or error.
	 */
	public static function from_array( array $data ): self|WP_Error {
		// Validate required fields
		if ( ! isset( $data['trigger'] ) || ! is_array( $data['trigger'] ) ) {
			return new WP_Error(
				'invalid_config',
				'Workflow config must include a trigger configuration.'
			);
		}

		if ( ! isset( $data['actions'] ) || ! is_array( $data['actions'] ) ) {
			return new WP_Error(
				'invalid_config',
				'Workflow must have actions.'
			);
		}
		$actions_data = $data['actions'];

		// Parse trigger.
		$trigger = TriggerConfig::from_array( $data['trigger'] );
		if ( is_wp_error( $trigger ) ) {
			return $trigger;
		}

		// Parse actions (map keyed by action ID).
		$actions = array();
		foreach ( $actions_data as $key => $action_data ) {
			// Ensure each action has its ID set (key = action ID in graph format).
			if ( is_string( $key ) && ! isset( $action_data['id'] ) ) {
				$action_data['id'] = $key;
			}

			$action = ActionConfig::from_array( $action_data );
			if ( is_wp_error( $action ) ) {
				return new WP_Error(
					'invalid_action',
					sprintf( 'Invalid action: %s', $action->get_error_message() )
				);
			}
			$actions[ $action->get_id() ] = $action;
		}

		// Parse entry_action.
		$entry_action = isset( $data['entry_action'] ) && is_string( $data['entry_action'] )
			? $data['entry_action']
			: null;

		// Parse layout (optional).
		$layout = isset( $data['layout'] ) && is_array( $data['layout'] ) ? $data['layout'] : null;

		return new self( $trigger, $actions, $entry_action, $layout );
	}

	/**
	 * Create from JSON string.
	 *
	 * @param string $json JSON string.
	 * @return self|WP_Error Config object or error.
	 */
	public static function from_json( string $json ): self|WP_Error {
		$data = json_decode( $json, true );

		if ( json_last_error() !== JSON_ERROR_NONE ) {
			return new WP_Error(
				'json_decode_error',
				'Failed to decode workflow config JSON: ' . json_last_error_msg()
			);
		}

		return self::from_array( $data );
	}

	/**
	 * Get trigger configuration.
	 *
	 * @return TriggerConfig Trigger config.
	 */
	public function get_trigger(): TriggerConfig {
		return $this->trigger;
	}

	/**
	 * Get all workflow actions.
	 *
	 * @return ActionConfig[] Action configurations.
	 */
	public function get_actions(): array {
		return $this->actions;
	}

	/**
	 * Get action by ID.
	 *
	 * @param string $id Action ID.
	 * @return ActionConfig|null Action configuration or null if not found.
	 */
	public function get_action( string $id ): ?ActionConfig {
		return $this->actions[ $id ] ?? null;
	}

	/**
	 * Get the entry action (first action to execute).
	 *
	 * @return ActionConfig|null Entry action or null if no actions.
	 */
	public function get_entry_action(): ?ActionConfig {
		if ( $this->entry_action && isset( $this->actions[ $this->entry_action ] ) ) {
			return $this->actions[ $this->entry_action ];
		}

		// Fallback: first action in the map.
		return ! empty( $this->actions ) ? reset( $this->actions ) : null;
	}

	/**
	 * Get the entry action ID.
	 *
	 * @return string|null Entry action ID or null.
	 */
	public function get_entry_action_id(): ?string {
		if ( $this->entry_action ) {
			return $this->entry_action;
		}

		// Fallback: first action's ID.
		$first = $this->get_entry_action();
		return $first ? $first->get_id() : null;
	}

	/**
	 * Get the next action ID for a given action, optionally following a specific route.
	 *
	 * @param string      $current_id Current action ID.
	 * @param string|null $route      Route name for branching actions.
	 * @return string|null Next action ID, or null if terminal.
	 */
	public function get_next_action_id( string $current_id, ?string $route = null ): ?string {
		$action = $this->get_action( $current_id );
		if ( ! $action ) {
			return null;
		}

		$next = $action->get_next();

		if ( null === $next ) {
			return null;
		}

		if ( is_string( $next ) ) {
			return $next;
		}

		// Array = route map. Look up the route, fallback to 'default'.
		if ( is_array( $next ) ) {
			if ( $route && isset( $next[ $route ] ) ) {
				return $next[ $route ];
			}
			return $next['default'] ?? null;
		}

		return null;
	}

	/**
	 * Get total number of actions.
	 *
	 * @return int Total action count.
	 */
	public function get_total_actions(): int {
		return count( $this->actions );
	}

	/**
	 * Get layout configuration.
	 *
	 * @return array|null Layout data.
	 */
	public function get_layout(): ?array {
		return $this->layout;
	}

	/**
	 * Convert to array.
	 *
	 * @return array Config data.
	 */
	public function to_array(): array {
		$actions_array = array();
		foreach ( $this->actions as $id => $action ) {
			$actions_array[ $id ] = $action->to_array();
		}

		$data = array(
			'trigger' => $this->trigger->to_array(),
			'actions' => $actions_array,
		);

		if ( null !== $this->entry_action ) {
			$data['entry_action'] = $this->entry_action;
		}

		if ( null !== $this->layout ) {
			$data['layout'] = $this->layout;
		}

		return $data;
	}

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

	/**
	 * Implement JsonSerializable for REST API responses.
	 *
	 * @return array Data to serialize.
	 */
	public function jsonSerialize(): array {
		return $this->to_array();
	}
}
