<?php
/**
 * Package Array Validator
 *
 * Validates the array structure returned by a package's package.php file.
 * Reports clear, actionable error messages for malformed packages so that
 * developers get immediate feedback during package development.
 *
 * @package Sequensy
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\Packages;

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

/**
 * Class PackageArrayValidator
 *
 * Static validator for package.php return values. Called by PackageLoader
 * after requiring a package.php file and before processing its contents.
 * Returns an array of error strings (empty array means valid).
 */
final class PackageArrayValidator {

	/**
	 * Validate a package.php return value.
	 *
	 * Checks the top-level structure and delegates to specialized validators
	 * for actions and triggers arrays.
	 *
	 * @param mixed  $package Package data to validate.
	 * @param string $slug    Package slug for error messages.
	 * @return array Array of error strings. Empty = valid.
	 */
	public static function validate( mixed $package, string $slug ): array {
		$errors = array();

		if ( ! is_array( $package ) ) {
			return array( "Package '{$slug}': package.php must return an array." );
		}

		// Validate boot callback if present.
		if ( isset( $package['boot'] ) && ! is_callable( $package['boot'] ) ) {
			$errors[] = "Package '{$slug}': 'boot' must be callable.";
		}

		// Validate api_factory callback if present.
		if ( isset( $package['api_factory'] ) && ! is_callable( $package['api_factory'] ) ) {
			$errors[] = "Package '{$slug}': 'api_factory' must be callable.";
		}

		// Validate actions array.
		if ( isset( $package['actions'] ) ) {
			if ( ! is_array( $package['actions'] ) ) {
				$errors[] = "Package '{$slug}': 'actions' must be an array.";
			} else {
				foreach ( $package['actions'] as $i => $action ) {
					$errors = array_merge( $errors, self::validate_action( $action, $slug, $i ) );
				}
			}
		}

		// Validate triggers array.
		if ( isset( $package['triggers'] ) ) {
			if ( ! is_array( $package['triggers'] ) ) {
				$errors[] = "Package '{$slug}': 'triggers' must be an array.";
			} else {
				foreach ( $package['triggers'] as $i => $trigger ) {
					$errors = array_merge( $errors, self::validate_trigger( $trigger, $slug, $i ) );
				}
			}
		}

		return $errors;
	}

	/**
	 * Validate a single action definition.
	 *
	 * @param mixed  $action Action definition to validate.
	 * @param string $slug   Package slug for error messages.
	 * @param int    $index  Action index within the actions array.
	 * @return array Array of error strings.
	 */
	private static function validate_action( mixed $action, string $slug, int $index ): array {
		$errors = array();
		$prefix = "Package '{$slug}' action[{$index}]";

		if ( ! is_array( $action ) ) {
			return array( "{$prefix}: must be an array." );
		}

		if ( empty( $action['type'] ) || ! is_string( $action['type'] ) ) {
			$errors[] = "{$prefix}: missing required string 'type'.";
		}

		if ( empty( $action['label'] ) || ! is_string( $action['label'] ) ) {
			$errors[] = "{$prefix}: missing required string 'label'.";
		}

		if ( ! isset( $action['handler'] ) || ! is_callable( $action['handler'] ) ) {
			$errors[] = "{$prefix}: missing required callable 'handler'.";
		}

		return $errors;
	}

	/**
	 * Validate a single trigger definition.
	 *
	 * @param mixed  $trigger Trigger definition to validate.
	 * @param string $slug    Package slug for error messages.
	 * @param int    $index   Trigger index within the triggers array.
	 * @return array Array of error strings.
	 */
	private static function validate_trigger( mixed $trigger, string $slug, int $index ): array {
		$errors = array();
		$prefix = "Package '{$slug}' trigger[{$index}]";

		if ( ! is_array( $trigger ) ) {
			return array( "{$prefix}: must be an array." );
		}

		if ( empty( $trigger['type'] ) || ! is_string( $trigger['type'] ) ) {
			$errors[] = "{$prefix}: missing required string 'type'.";
		}

		if ( empty( $trigger['hook'] ) || ! is_string( $trigger['hook'] ) ) {
			$errors[] = "{$prefix}: missing required string 'hook'.";
		}

		if ( ! isset( $trigger['build_context'] ) || ! is_callable( $trigger['build_context'] ) ) {
			$errors[] = "{$prefix}: missing required callable 'build_context'.";
		}

		return $errors;
	}
}
