<?php
/**
 * Structural validation for emitted FAQPage 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 FAQPage mainEntity shape on the canonical emitted graph.
 *
 * Incomplete blocks are withheld before emission; this rule catches malformed
 * custom/third-party FAQPage that still reaches the published graph.
 * Duplicate questions are recommendations, never blockers.
 */
class FaqPageStructureRule implements RuleInterface {

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

	/**
	 * {@inheritdoc}
	 */
	public function supports( ValidationContext $context ) {
		foreach ( $context->get_entities() as $entity ) {
			if ( in_array( 'FAQPage', $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( 'FAQPage', $entity['types'] ?? array(), true ) ) {
				continue;
			}
			$issues = array_merge( $issues, $this->evaluate_faq_entity( $entity, $context ) );
		}

		return $issues;
	}

	/**
	 * Validate one FAQPage entity.
	 *
	 * @param array             $entity  Normalized entity.
	 * @param ValidationContext $context Context.
	 * @return ValidationIssue[]
	 */
	private function evaluate_faq_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'] : '';
		$entities = isset( $data['mainEntity'] ) ? $data['mainEntity'] : null;

		if ( null === $entities || ( is_array( $entities ) && empty( $entities ) ) || '' === $entities ) {
			$issues[] = $this->issue(
				'faq.main_entity.missing',
				Severity::ERROR,
				$entity,
				$context,
				$path . '.mainEntity'
			);
			return $issues;
		}

		$list = ValueInspector::is_list( $entities ) ? $entities : array( $entities );
		$complete          = 0;
		$seen_questions    = array();
		$duplicate_found   = false;

		foreach ( $list as $index => $item ) {
			$item_path = $path . '.mainEntity[' . (int) $index . ']';
			if ( ! is_array( $item ) ) {
				$issues[] = $this->issue(
					'faq.question.incomplete',
					Severity::ERROR,
					$entity,
					$context,
					$item_path
				);
				continue;
			}

			$question = $this->first_text( $item, array( 'name', 'text' ) );
			$answer   = isset( $item['acceptedAnswer'] ) ? $item['acceptedAnswer'] : null;
			$answer_text = '';
			if ( is_array( $answer ) ) {
				$answer_text = $this->first_text( $answer, array( 'text', 'name' ) );
			} elseif ( is_string( $answer ) || is_numeric( $answer ) ) {
				$answer_text = trim( (string) $answer );
			}

			if ( '' === $question || '' === $answer_text ) {
				$issues[] = $this->issue(
					'faq.question.incomplete',
					Severity::ERROR,
					$entity,
					$context,
					$item_path
				);
				continue;
			}

			++$complete;
			$norm = $this->normalize_question( $question );
			if ( '' !== $norm && isset( $seen_questions[ $norm ] ) ) {
				$duplicate_found = true;
			} elseif ( '' !== $norm ) {
				$seen_questions[ $norm ] = true;
			}
		}

		if ( $complete < 1 ) {
			$issues[] = $this->issue(
				'faq.incomplete_pair',
				Severity::ERROR,
				$entity,
				$context,
				$path . '.mainEntity'
			);
		}

		if ( $duplicate_found ) {
			$issues[] = $this->issue(
				'faq.duplicate_question',
				Severity::RECOMMENDATION,
				$entity,
				$context,
				$path . '.mainEntity'
			);
		}

		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 $question Question text.
	 * @return string
	 */
	private function normalize_question( $question ) {
		$question = wp_strip_all_tags( (string) $question );
		$question = preg_replace( '/\s+/u', ' ', $question );
		$question = is_string( $question ) ? trim( $question ) : '';
		if ( function_exists( 'mb_strtolower' ) ) {
			return mb_strtolower( $question, 'UTF-8' );
		}
		return strtolower( $question );
	}

	/**
	 * @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'   => 'FAQPage',
				'entity_id'     => $entity['id'] ?? '',
				'path'          => $path,
				'source'        => $context->get_source(),
				'fixable'       => true,
				'fix_action'    => 'edit_faq_block',
				'documentation' => 'faq_page',
				'title_key'     => $code . '.title',
				'message_key'   => $code . '.message',
			)
		);
	}
}
