<?php
/**
 * Action Definition Value Object
 *
 * Immutable value object containing all metadata for an action definition.
 * This decouples action metadata from implementation, allowing declarative registration.
 *
 * @package Sequensy
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\Actions;

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

/**
 * Class ActionDefinition
 *
 * Represents the complete metadata for an action without requiring instantiation
 * of the action handler class. This enables:
 * - Lazy loading of action handlers
 * - Declarative action registration
 * - Schema/metadata inspection without full class instantiation
 * - Reduced boilerplate in action classes
 */
final class ActionDefinition {

	/**
	 * Constructor.
	 *
	 * @param string      $type           Action type identifier (e.g., 'mc_add_subscriber').
	 * @param string      $label          Human-readable label (e.g., 'Add Subscriber to Mailchimp').
	 * @param string      $description    Action description.
	 * @param string      $integration    Integration slug (e.g., 'mailchimp', 'kit').
	 * @param array       $schema         Configuration schema (field definitions).
	 * @param array       $output_fields  Output field definitions.
	 * @param array       $required_fields Required field keys for validation.
	 * @param string      $handler_class  Fully qualified class name of the action handler.
	 * @param string|null $category       Optional category for grouping (e.g., 'subscriber', 'record', 'tag').
	 * @param string|null $icon           Optional icon identifier.
	 * @param array       $options        Additional options (e.g., 'requires_email' => true).
	 */
	public function __construct(
		public readonly string $type,
		public readonly string $label,
		public readonly string $description,
		public readonly string $integration,
		public readonly array $schema,
		public readonly array $output_fields,
		public readonly array $required_fields,
		public readonly string $handler_class,
		public readonly ?string $category = null,
		public readonly ?string $icon = null,
		public readonly array $options = array(),
	) {}

	/**
	 * Check if action requires email validation.
	 *
	 * Defaults to true for subscriber and tag categories.
	 *
	 * @return bool True if email validation is required.
	 */
	public function requires_email(): bool {
		if ( isset( $this->options['requires_email'] ) ) {
			return (bool) $this->options['requires_email'];
		}
		return in_array( $this->category, array( 'subscriber', 'tag' ), true );
	}

	/**
	 * Create from array.
	 *
	 * @param array $data Definition data.
	 * @return self New instance.
	 * @throws \InvalidArgumentException If required fields are missing.
	 */
	public static function from_array( array $data ): self {
		$required = array( 'type', 'label', 'description', 'integration', 'schema', 'output_fields', 'handler_class' );

		foreach ( $required as $field ) {
			if ( ! isset( $data[ $field ] ) ) {
				// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message.
				throw new \InvalidArgumentException( "Missing required field: {$field}" );
			}
		}

		return new self(
			type: $data['type'],
			label: $data['label'],
			description: $data['description'],
			integration: $data['integration'],
			schema: $data['schema'],
			output_fields: $data['output_fields'],
			required_fields: $data['required_fields'] ?? array(),
			handler_class: $data['handler_class'],
			category: $data['category'] ?? null,
			icon: $data['icon'] ?? null,
			options: $data['options'] ?? array(),
		);
	}

	/**
	 * Convert to array.
	 *
	 * @return array Definition as array.
	 */
	public function to_array(): array {
		return array(
			'type'            => $this->type,
			'label'           => $this->label,
			'description'     => $this->description,
			'integration'     => $this->integration,
			'schema'          => $this->schema,
			'output_fields'   => $this->output_fields,
			'required_fields' => $this->required_fields,
			'handler_class'   => $this->handler_class,
			'category'        => $this->category,
			'icon'            => $this->icon,
			'options'         => $this->options,
		);
	}

	/**
	 * Create a new ActionDefinition with a subset of schema fields.
	 *
	 * @param array $allowed_field_names Field names to keep in the schema.
	 * @return self New instance with filtered schema and required fields.
	 */
	public function with_filtered_schema( array $allowed_field_names ): self {
		$filtered_schema   = array_values(
			array_filter(
				$this->schema,
				fn( $field ) => in_array( $field['name'] ?? '', $allowed_field_names, true )
			)
		);
		$filtered_required = array_values( array_intersect( $this->required_fields, $allowed_field_names ) );

		return new self(
			type: $this->type,
			label: $this->label,
			description: $this->description,
			integration: $this->integration,
			schema: $filtered_schema,
			output_fields: $this->output_fields,
			required_fields: $filtered_required,
			handler_class: $this->handler_class,
			category: $this->category,
			icon: $this->icon,
			options: $this->options,
		);
	}

	/**
	 * Get output field by key.
	 *
	 * @param string $key Field key.
	 * @return array|null Field definition or null if not found.
	 */
	public function get_output_field( string $key ): ?array {
		foreach ( $this->output_fields as $field ) {
			if ( isset( $field['key'] ) && $field['key'] === $key ) {
				return $field;
			}
		}
		return null;
	}
}
