<?php
/**
 * Action Validator
 *
 * Validates action configuration including existence, availability, and required fields.
 *
 * @package Sequensy\Validation
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\Validation;

use Sequensy\Validation\Contracts\ValidatorInterface;
use Sequensy\Validation\Concerns\CollectsErrors;
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Class ActionValidator
 *
 * Validates action configuration.
 */
class ActionValidator implements ValidatorInterface {

	use CollectsErrors;

	/**
	 * Action registry instance.
	 *
	 * @var object
	 */
	private object $action_registry;

	/**
	 * Integration validator instance.
	 *
	 * @var IntegrationValidator
	 */
	private IntegrationValidator $integration_validator;

	/**
	 * Constructor.
	 *
	 * @param object               $action_registry       Action registry instance.
	 * @param IntegrationValidator $integration_validator Integration validator instance.
	 */
	public function __construct( object $action_registry, IntegrationValidator $integration_validator ) {
		$this->action_registry       = $action_registry;
		$this->integration_validator = $integration_validator;
	}

	/**
	 * Validate actions configuration.
	 *
	 * @param array $config  Workflow configuration.
	 * @param array $context Additional context (unused).
	 * @return array Validation result.
	 */
	public function validate( array $config, array $context = array() ): array {
		$this->clear_errors();

		$actions = $config['actions'] ?? array();

		// Validate actions exist.
		if ( empty( $actions ) ) {
			$this->add_error(
				'missing_actions',
				'Workflow must have at least one action.',
				array( 'field' => 'actions' )
			);
			return $this->get_result();
		}

		// Validate actions.
		$this->validate_unique_action_labels( $actions );
		$this->validate_actions( $actions );

		return $this->get_result();
	}

	/**
	 * Validate all actions in the workflow.
	 *
	 * @param array $actions Array of action configurations.
	 * @return void
	 */
	private function validate_actions( array $actions ): void {
		foreach ( $actions as $index => $action ) {
			$this->validate_action( $action, $index );
		}
	}

	/**
	 * Validate a single action.
	 *
	 * @param array      $action Action configuration.
	 * @param int|string $index  Action index for error context.
	 * @return void
	 */
	private function validate_action( array $action, int|string $index ): void {
		$action_label = $this->get_action_label( $action, $index );

		// Check action type exists.
		if ( empty( $action['type'] ) ) {
			$this->add_error(
				'action_missing_type',
				sprintf( '%s is missing a type.', $action_label ),
				array( 'action_index' => $index )
			);
			return;
		}

		$type = $action['type'];

		// Check action is registered.
		if ( ! $this->action_registry->has_action( $type ) ) {
			// Integration-type packages are deferred at boot. Try loading on-demand.
			if ( str_contains( $type, '/' ) ) {
				$integration = explode( '/', $type, 2 )[0];
				do_action( 'sequensy_before_execute_action', $type, $integration );
			}

			if ( ! $this->action_registry->has_action( $type ) ) {
				$this->add_error(
					'invalid_action_type',
					sprintf( '%s has unknown type: %s', $action_label, $type ),
					array(
						'action_index' => $index,
						'action_type'  => $type,
					)
				);
				return;
			}
		}

		$action_handler = $this->action_registry->get_action( $type );

		// Check plugin availability.
		$this->validate_action_availability( $action_handler, $index, $action_label );

		// Check required fields.
		$this->validate_action_required_fields( $action, $action_handler, $index, $action_label );

		// Check integration connection (for API-based integrations).
		$this->validate_integration_connection( $action_handler, $index, $action_label );
	}

	/**
	 * Validate action plugin/integration availability.
	 *
	 * Plugin-based actions are now provided by marketplace packages,
	 * which handle their own availability checks.
	 *
	 * @param object     $action_handler Action handler instance.
	 * @param int|string $index          Action index.
	 * @param string     $action_label   Action label for messages.
	 * @return void
	 */
	private function validate_action_availability(
		object $action_handler,
		int|string $index,
		string $action_label
	): void {
		// Marketplace packages handle their own availability checks.
	}

	/**
	 * Validate action required fields are configured.
	 *
	 * @param array      $action         Action configuration.
	 * @param object     $action_handler Action handler instance.
	 * @param int|string $index          Action index.
	 * @param string     $action_label   Action label for messages.
	 * @return void
	 */
	private function validate_action_required_fields(
		array $action,
		object $action_handler,
		int|string $index,
		string $action_label
	): void {
		$schema = $action_handler->get_schema();
		$config = isset( $action['config'] ) ? $action['config'] : array();

		foreach ( $schema as $key => $field_def ) {
			// Support both keyed schemas ({field_name: def}) and indexed schemas ([{name: field_name, ...}]).
			$field_name = is_string( $key ) && ! is_numeric( $key )
				? $key
				: ( $field_def['name'] ?? '' );

			if ( empty( $field_name ) ) {
				continue;
			}

			if ( ! empty( $field_def['required'] ) ) {
				// Check if field exists and is not empty string.
				if ( ! isset( $config[ $field_name ] ) || '' === $config[ $field_name ] ) {
					$field_label = isset( $field_def['label'] ) ? $field_def['label'] : $field_name;
					$this->add_error(
						'action_missing_required_field',
						sprintf( '%s is missing required field: %s', $action_label, $field_label ),
						array(
							'action_index' => $index,
							'field'        => $field_name,
							'field_label'  => $field_label,
						)
					);
				}
			}
		}
	}

	/**
	 * Validate third-party integration connection status.
	 *
	 * @param object     $action_handler Action handler instance.
	 * @param int|string $index          Action index.
	 * @param string     $action_label   Action label for messages.
	 * @return void
	 */
	private function validate_integration_connection(
		object $action_handler,
		int|string $index,
		string $action_label
	): void {
		if ( ! method_exists( $action_handler, 'get_integration' ) ) {
			return;
		}

		$integration = $action_handler->get_integration();

		// Delegate to integration validator.
		$result = $this->integration_validator->validate_connection( $integration, $index, $action_label );
		if ( ! empty( $result['errors'] ) ) {
			$this->merge_errors( $result['errors'] );
		}
	}

	/**
	 * Validate no duplicate action labels exist.
	 *
	 * @param array $actions Array of action configurations.
	 * @return void
	 */
	private function validate_unique_action_labels( array $actions ): void {
		$labels = array();

		foreach ( $actions as $index => $action ) {
			if ( empty( $action['label'] ) ) {
				continue;
			}

			$label = $action['label'];
			if ( isset( $labels[ $label ] ) ) {
				$first  = is_string( $labels[ $label ] ) ? '"' . $labels[ $label ] . '"' : $labels[ $label ] + 1;
				$second = is_string( $index ) ? '"' . $index . '"' : $index + 1;
				$this->add_error(
					'duplicate_action_label',
					sprintf(
						/* translators: 1: label, 2: first position, 3: second position */
						'Duplicate action label "%1$s" at positions %2$s and %3$s.',
						$label,
						$first,
						$second
					),
					array(
						'label'        => $label,
						'first_index'  => $labels[ $label ],
						'second_index' => $index,
					)
				);
			} else {
				$labels[ $label ] = $index;
			}
		}
	}

	/**
	 * Get a display label for an action.
	 *
	 * @param array      $action Action configuration.
	 * @param int|string $index  Action index.
	 * @return string Action label.
	 */
	public function get_action_label( array $action, int|string $index ): string {
		if ( ! empty( $action['label'] ) ) {
			return $action['label'];
		}

		if ( ! empty( $action['name'] ) ) {
			return $action['name'];
		}

		if ( is_string( $index ) ) {
			return sprintf( 'Action "%s"', $index );
		}

		return sprintf( 'Action #%d', $index + 1 );
	}
}
