<?php
/**
 * Schema-validation result value object.
 *
 * @package SEOvaultSEO\Schema\Validation
 */

namespace SEOvaultSEO\Schema\Validation;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Aggregates structured issues into a deterministic validation result.
 */
class ValidationResult {

	/**
	 * Collected issues.
	 *
	 * @var ValidationIssue[]
	 */
	private $issues = array();

	/**
	 * Recognized entity summaries.
	 *
	 * @var array
	 */
	private $entities = array();

	/**
	 * Number of checks that passed.
	 *
	 * @var int
	 */
	private $passed_checks = 0;

	/**
	 * Stable normalized schema hash.
	 *
	 * @var string
	 */
	private $schema_hash = '';

	/**
	 * UNIX timestamp of the completed check.
	 *
	 * @var int
	 */
	private $validated_at = 0;

	/**
	 * Schema source.
	 *
	 * @var string
	 */
	private $source;

	/**
	 * Whether validation completed.
	 *
	 * @var bool
	 */
	private $checked = false;

	/**
	 * Create a result for a schema source.
	 *
	 * @param string $source Schema source.
	 */
	public function __construct( $source = 'generated' ) {
		$this->source = in_array( $source, array( 'generated', 'custom', 'live' ), true ) ? $source : 'generated';
	}

	/**
	 * Add a validation issue.
	 *
	 * @param ValidationIssue $issue Issue to add.
	 * @return void
	 */
	public function add_issue( ValidationIssue $issue ) {
		$this->issues[] = $issue;
	}

	/**
	 * Set recognized entities.
	 *
	 * @param array $entities Entity summaries.
	 * @return void
	 */
	public function set_entities( $entities ) {
		$this->entities = $entities;
	}

	/**
	 * Set the number of checks that completed without issues.
	 *
	 * @param int $passed_checks Passed check count.
	 * @return void
	 */
	public function set_passed_checks( $passed_checks ) {
		$this->passed_checks = max( 0, (int) $passed_checks );
	}

	/**
	 * Set the stable schema hash.
	 *
	 * @param string $schema_hash Schema hash.
	 * @return void
	 */
	public function set_schema_hash( $schema_hash ) {
		$this->schema_hash = $schema_hash;
	}

	/**
	 * Mark the result as checked at a UNIX timestamp.
	 *
	 * @param int $validated_at Validation timestamp.
	 * @return void
	 */
	public function mark_checked( $validated_at ) {
		$this->checked      = true;
		$this->validated_at = max( 0, (int) $validated_at );
	}

	/**
	 * Export a deterministic result array.
	 *
	 * @return array
	 */
	public function to_array() {
		$issues = $this->get_sorted_unique_issues();
		$counts = array(
			Severity::ERROR          => 0,
			Severity::WARNING        => 0,
			Severity::RECOMMENDATION => 0,
			Severity::INFO           => 0,
		);

		foreach ( $issues as $issue ) {
			++$counts[ $issue->get_severity() ];
		}

		return array(
			'status'           => $this->get_status( $counts ),
			'errors'           => $counts[ Severity::ERROR ],
			'warnings'         => $counts[ Severity::WARNING ],
			'recommendations'  => $counts[ Severity::RECOMMENDATION ],
			'info'             => $counts[ Severity::INFO ],
			'passed_checks'    => $this->passed_checks,
			'entities'         => $this->entities,
			'issues'           => array_map(
				function ( $issue ) {
					return $issue->to_array();
				},
				$issues
			),
			'rule_set_version' => RuleSetVersion::get_current(),
			'schema_hash'      => $this->schema_hash,
			'validated_at'     => $this->validated_at,
			'source'           => $this->source,
		);
	}

	/**
	 * Get a status from severity counts.
	 *
	 * @param array $counts Severity counts.
	 * @return string
	 */
	private function get_status( $counts ) {
		if ( ! $this->checked ) {
			return 'not_checked';
		}
		if ( $counts[ Severity::ERROR ] > 0 ) {
			return 'invalid';
		}
		if ( $counts[ Severity::WARNING ] > 0 ) {
			return 'warning';
		}
		if ( $counts[ Severity::RECOMMENDATION ] > 0 ) {
			return 'valid_with_recommendations';
		}
		return 'valid';
	}

	/**
	 * Get de-duplicated issues in deterministic order.
	 *
	 * @return ValidationIssue[]
	 */
	private function get_sorted_unique_issues() {
		$issues = array();
		foreach ( $this->issues as $issue ) {
			$issues[ $issue->get_signature() ] = $issue;
		}

		$issues = array_values( $issues );
		usort(
			$issues,
			function ( ValidationIssue $left, ValidationIssue $right ) {
				$left_data  = $left->to_array();
				$right_data = $right->to_array();
				$left_sort  = array(
					Severity::get_priority( $left->get_severity() ),
					$left_data['entity_type'],
					$left_data['entity_id'],
					$left_data['path'],
					$left->get_code(),
				);
				$right_sort = array(
					Severity::get_priority( $right->get_severity() ),
					$right_data['entity_type'],
					$right_data['entity_id'],
					$right_data['path'],
					$right->get_code(),
				);

				return $left_sort <=> $right_sort;
			}
		);

		return $issues;
	}
}
