<?php
/**
 * Trigger Validator
 *
 * Validates trigger configuration including existence, availability, and filters.
 *
 * @package Sequensy\Validation
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\Validation;

use Sequensy\Validation\Contracts\ValidatorInterface;
use Sequensy\Validation\Concerns\CollectsErrors;
use Sequensy\Config\OperatorConfig;

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

/**
 * Class TriggerValidator
 *
 * Validates trigger configuration.
 */
class TriggerValidator implements ValidatorInterface {

	use CollectsErrors;

	/**
	 * Trigger registry instance.
	 *
	 * @var object
	 */
	private object $trigger_registry;

	/**
	 * Constructor.
	 *
	 * @param object $trigger_registry Trigger registry instance.
	 */
	public function __construct( object $trigger_registry ) {
		$this->trigger_registry = $trigger_registry;
	}

	/**
	 * Validate trigger 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();

		// Validate trigger exists.
		$this->validate_has_trigger( $config );

		// Validate trigger if present.
		if ( isset( $config['trigger'] ) && ! empty( $config['trigger']['type'] ) ) {
			$this->validate_trigger( $config['trigger'] );
			$this->validate_trigger_filters( $config );
		}

		return $this->get_result();
	}

	/**
	 * Validate that workflow has a trigger configured.
	 *
	 * @param array $config Workflow configuration.
	 * @return void
	 */
	private function validate_has_trigger( array $config ): void {
		if ( ! isset( $config['trigger'] ) || empty( $config['trigger']['type'] ) ) {
			$this->add_error(
				'missing_trigger',
				'Workflow must have a trigger configured.',
				array( 'field' => 'trigger' )
			);
		}
	}

	/**
	 * Validate trigger configuration.
	 *
	 * @param array $trigger Trigger configuration.
	 * @return void
	 */
	private function validate_trigger( array $trigger ): void {
		$type = $trigger['type'];

		// Check trigger is registered.
		if ( ! $this->trigger_registry->has_trigger( $type ) ) {
			$this->add_error(
				'invalid_trigger_type',
				sprintf( 'Unknown trigger type: %s', $type ),
				array( 'trigger_type' => $type )
			);
			return;
		}

		// Check plugin availability for plugin-based triggers.
		$trigger_handler = $this->trigger_registry->get_trigger( $type );
		$this->validate_trigger_availability( $trigger_handler );
	}

	/**
	 * Validate trigger plugin/integration availability.
	 *
	 * Plugin-based triggers are now provided by marketplace packages,
	 * which handle their own availability checks.
	 *
	 * @param object $trigger_handler Trigger handler instance.
	 * @return void
	 */
	private function validate_trigger_availability( object $trigger_handler ): void {
		// Marketplace packages handle their own availability checks.
	}

	/**
	 * Validate trigger filters reference valid fields and use valid operators.
	 *
	 * @param array $config Workflow configuration.
	 * @return void
	 */
	private function validate_trigger_filters( array $config ): void {
		if ( ! isset( $config['trigger']['filters'] ) ) {
			return; // No filters to validate.
		}

		$filters      = $config['trigger']['filters'];
		$trigger_type = $config['trigger']['type'] ?? '';

		// Get available fields for this trigger.
		$trigger_handler = $this->trigger_registry->get_trigger( $trigger_type );
		if ( ! $trigger_handler || ! method_exists( $trigger_handler, 'get_available_fields' ) ) {
			return;
		}

		$available_fields = $trigger_handler->get_available_fields();
		$field_map        = array();
		foreach ( $available_fields as $field ) {
			$field_map[ $field['name'] ] = $field['type'];
		}

		$this->validate_filter_node( $filters, $field_map );
	}

	/**
	 * Recursively validate a filter node (condition or group).
	 *
	 * @param array $node      Filter node.
	 * @param array $field_map Map of field name => field type.
	 * @return void
	 */
	private function validate_filter_node( array $node, array $field_map ): void {
		// Check if this is a group (has 'logic' key).
		if ( isset( $node['logic'] ) ) {
			$conditions = isset( $node['conditions'] ) ? $node['conditions'] : array();
			foreach ( $conditions as $condition ) {
				if ( is_array( $condition ) ) {
					$this->validate_filter_node( $condition, $field_map );
				}
			}
			return;
		}

		// Validate individual condition.
		$this->validate_filter_condition( $node, $field_map );
	}

	/**
	 * Validate a single filter condition.
	 *
	 * @param array $condition Filter condition.
	 * @param array $field_map Map of field name => field type.
	 * @return void
	 */
	private function validate_filter_condition( array $condition, array $field_map ): void {
		$field    = isset( $condition['field'] ) ? $condition['field'] : '';
		$operator = isset( $condition['operator'] ) ? $condition['operator'] : '';

		// Check field is specified.
		if ( empty( $field ) ) {
			$this->add_error(
				'filter_missing_field',
				'Filter condition is missing a field.',
				array( 'filter' => $condition )
			);
			return;
		}

		// Check field exists in trigger's available fields.
		if ( ! isset( $field_map[ $field ] ) ) {
			$this->add_error(
				'filter_invalid_field',
				sprintf( 'Filter references unknown trigger field: %s', $field ),
				array(
					'field'     => $field,
					'available' => array_keys( $field_map ),
				)
			);
			return;
		}

		// Check operator is specified.
		if ( empty( $operator ) ) {
			$this->add_error(
				'filter_missing_operator',
				sprintf( 'Filter condition for field "%s" is missing an operator.', $field ),
				array( 'field' => $field )
			);
			return;
		}

		// Validate operator is valid for field type.
		$field_type = $field_map[ $field ];
		if ( ! $this->is_valid_filter_operator( $operator, $field_type ) ) {
			$valid_operators = $this->get_valid_filter_operators( $field_type );
			$this->add_error(
				'filter_invalid_operator',
				sprintf(
					'Operator "%s" is not valid for %s field "%s". Valid operators: %s',
					$operator,
					$field_type,
					$field,
					implode( ', ', $valid_operators )
				),
				array(
					'field'           => $field,
					'operator'        => $operator,
					'field_type'      => $field_type,
					'valid_operators' => $valid_operators,
				)
			);
		}
	}

	/**
	 * Check if operator is valid for field type.
	 *
	 * @param string $operator   Operator name.
	 * @param string $field_type Field type.
	 * @return bool True if valid, false otherwise.
	 */
	private function is_valid_filter_operator( string $operator, string $field_type ): bool {
		$valid_operators = $this->get_valid_filter_operators( $field_type );
		return in_array( $operator, $valid_operators, true );
	}

	/**
	 * Get valid operators for a field type.
	 *
	 * @param string $field_type Field type.
	 * @return array Array of valid operator names.
	 */
	private function get_valid_filter_operators( string $field_type ): array {
		return OperatorConfig::get_operators_for_type( $field_type );
	}
}
