<?php
/**
 * Structural validation for emitted HowTo entities.
 *
 * @package SEOvaultSEO\Schema\Validation\Rules\Core
 */

namespace SEOvaultSEO\Schema\Validation\Rules\Core;

use SEOvaultSEO\Schema\Validation\RuleInterface;
use SEOvaultSEO\Schema\Validation\Severity;
use SEOvaultSEO\Schema\Validation\ValidationContext;
use SEOvaultSEO\Schema\Validation\ValidationIssue;
use SEOvaultSEO\Schema\Validation\ValueInspector;

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

/**
 * Validates HowTo name + step shape on the canonical emitted graph.
 *
 * Incomplete blocks are withheld before emission; this rule catches malformed
 * custom/third-party HowTo that still reaches the published graph.
 */
class HowToStructureRule implements RuleInterface {

	/**
	 * {@inheritdoc}
	 */
	public function get_code() {
		return 'schema.howto';
	}

	/**
	 * {@inheritdoc}
	 */
	public function supports( ValidationContext $context ) {
		foreach ( $context->get_entities() as $entity ) {
			if ( in_array( 'HowTo', $entity['types'] ?? array(), true ) ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * {@inheritdoc}
	 */
	public function evaluate( ValidationContext $context ) {
		$issues = array();

		foreach ( $context->get_entities() as $entity ) {
			if ( ! in_array( 'HowTo', $entity['types'] ?? array(), true ) ) {
				continue;
			}
			$issues = array_merge( $issues, $this->evaluate_howto_entity( $entity, $context ) );
		}

		return $issues;
	}

	/**
	 * Validate one HowTo entity.
	 *
	 * @param array             $entity  Normalized entity.
	 * @param ValidationContext $context Context.
	 * @return ValidationIssue[]
	 */
	private function evaluate_howto_entity( array $entity, ValidationContext $context ) {
		$issues = array();
		$data   = isset( $entity['data'] ) && is_array( $entity['data'] ) ? $entity['data'] : array();
		$path   = isset( $entity['path'] ) ? (string) $entity['path'] : '';

		$name = $this->first_text( $data, array( 'name' ) );
		if ( '' === $name ) {
			$issues[] = $this->issue(
				'howto.name.missing',
				Severity::ERROR,
				$entity,
				$context,
				$path . '.name'
			);
		}

		$steps = isset( $data['step'] ) ? $data['step'] : null;
		if ( null === $steps || ( is_array( $steps ) && empty( $steps ) ) || '' === $steps ) {
			$issues[] = $this->issue(
				'howto.steps.missing',
				Severity::ERROR,
				$entity,
				$context,
				$path . '.step'
			);
			return $issues;
		}

		$list     = ValueInspector::is_list( $steps ) ? $steps : array( $steps );
		$complete = 0;

		foreach ( $list as $index => $item ) {
			$item_path = $path . '.step[' . (int) $index . ']';
			if ( is_string( $item ) || is_numeric( $item ) ) {
				if ( '' !== trim( (string) $item ) ) {
					++$complete;
				} else {
					$issues[] = $this->issue(
						'howto.step.incomplete',
						Severity::ERROR,
						$entity,
						$context,
						$item_path
					);
				}
				continue;
			}
			if ( ! is_array( $item ) ) {
				$issues[] = $this->issue(
					'howto.step.incomplete',
					Severity::ERROR,
					$entity,
					$context,
					$item_path
				);
				continue;
			}

			$step_text = $this->first_text( $item, array( 'text', 'name' ) );
			if ( '' === $step_text ) {
				$issues[] = $this->issue(
					'howto.step.incomplete',
					Severity::ERROR,
					$entity,
					$context,
					$item_path
				);
				continue;
			}
			++$complete;
		}

		if ( $complete < 1 ) {
			$issues[] = $this->issue(
				'howto.steps.missing',
				Severity::ERROR,
				$entity,
				$context,
				$path . '.step'
			);
		}

		return $issues;
	}

	/**
	 * @param array    $data Object.
	 * @param string[] $keys Keys.
	 * @return string
	 */
	private function first_text( array $data, array $keys ) {
		foreach ( $keys as $key ) {
			if ( ! array_key_exists( $key, $data ) ) {
				continue;
			}
			$value = $data[ $key ];
			if ( is_string( $value ) || is_numeric( $value ) ) {
				$text = trim( (string) $value );
				if ( '' !== $text ) {
					return $text;
				}
			}
		}
		return '';
	}

	/**
	 * @param string            $code     Issue code.
	 * @param string            $severity Severity.
	 * @param array             $entity   Entity.
	 * @param ValidationContext $context  Context.
	 * @param string            $path     Path.
	 * @return ValidationIssue
	 */
	private function issue( $code, $severity, array $entity, ValidationContext $context, $path ) {
		return new ValidationIssue(
			array(
				'code'          => $code,
				'severity'      => $severity,
				'category'      => 'core',
				'entity_type'   => 'HowTo',
				'entity_id'     => $entity['id'] ?? '',
				'path'          => $path,
				'source'        => $context->get_source(),
				'fixable'       => true,
				'fix_action'    => 'edit_howto_block',
				'documentation' => 'how_to',
				'title_key'     => $code . '.title',
				'message_key'   => $code . '.message',
			)
		);
	}
}
