<?php
/**
 * V2 foundational context collector.
 *
 * Builds automatic context-managed schema configs separately from legacy
 * global schema entries. Does not emit specialized types.
 *
 * @package SEOvaultSEO\Schema
 */

namespace SEOvaultSEO\Schema;

use SEOvaultSEO\Schema\Readiness\SchemaReadinessEvaluator;

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

/**
 * Collects foundational schema configurations from objective WP context.
 */
class FoundationalContextCollector {

	/**
	 * Stable emission order for foundational types.
	 *
	 * @var string[]
	 */
	const EMISSION_ORDER = array(
		'WebSite',
		'Organization',
		'Person',
		'SearchAction',
		'WebPage',
		'BreadcrumbList',
		'Article',
	);

	/**
	 * Collect enabled foundational schema configs for an object/context.
	 *
	 * @param \WP_Post|\WP_Term|\WP_User|null $object Explicit render object.
	 * @return array<int,array>
	 */
	public function collect_configs( $object = null ) {
		$applicable = $this->applicable_types( $object );
		$type_map   = SchemaUtils::get_schema_types();
		$configs    = array();

		foreach ( self::EMISSION_ORDER as $type ) {
			if ( ! in_array( $type, $applicable, true ) ) {
				continue;
			}
			if ( empty( $type_map[ $type ] ) || ! class_exists( $type_map[ $type ] ) ) {
				continue;
			}

			$instance = new $type_map[ $type ]();
			if ( ! method_exists( $instance, 'schema_data' ) ) {
				continue;
			}

			$data = $instance->schema_data();
			if ( ! is_array( $data ) ) {
				continue;
			}

			$data['type']    = $type;
			$data['enabled'] = true;
			// Context already decided inclusion; avoid legacy show_on/not_show_on re-gating.
			$data['show_on'] = array(
				'rules'        => array( 'basic-global' ),
				'specific'     => array(),
				'specificText' => array(),
			);
			unset( $data['not_show_on'] );

			$data      = $this->adjust_config_for_context( $data, $type, $applicable );
			$configs[] = $data;
		}

		/**
		 * Filters foundational schema configs before rendering.
		 *
		 * @param array                                $configs Config list.
		 * @param \WP_Post|\WP_Term|\WP_User|null       $object  Object.
		 * @param string[]                             $applicable Applicable types.
		 */
		$filtered = apply_filters( 'seovaultseo_schema_v2_foundational_configs', $configs, $object, $applicable );
		if ( ! is_array( $filtered ) ) {
			return $configs;
		}

		$clean = array();
		foreach ( $filtered as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}
			$type = isset( $item['type'] ) ? (string) $item['type'] : '';
			if ( '' === $type || ! SchemaTypePolicyRegistry::is_foundational( $type ) ) {
				continue;
			}
			$item['enabled'] = true;
			$clean[]         = $item;
		}

		return $clean;
	}

	/**
	 * Determine which foundational types apply for the current context.
	 *
	 * @param \WP_Post|\WP_Term|\WP_User|null $object Object.
	 * @return string[]
	 */
	public function applicable_types( $object = null ) {
		$types = array( 'WebSite' );

		$identity = $this->publisher_identity();
		if ( 'organization' === $identity || 'both' === $identity ) {
			$types[] = 'Organization';
		}
		if ( 'person' === $identity || 'both' === $identity ) {
			$types[] = 'Person';
		} elseif ( $this->needs_author_person( $object ) ) {
			$types[] = 'Person';
		}

		if ( $this->has_valid_search_target() ) {
			if ( SchemaArchitectureSettings::is_automatic_component_enabled( 'search_action' ) ) {
				$types[] = 'SearchAction';
			}
		}

		if ( $this->is_front_context( $object ) ) {
			return array_values( array_unique( $types ) );
		}

		if ( $object instanceof \WP_Post ) {
			$types[] = 'WebPage';
			if ( $this->is_article_eligible( $object ) ) {
				$types[] = $this->article_subtype();
			}
			if ( $this->has_valid_breadcrumb_structure( $object )
				&& SchemaArchitectureSettings::is_automatic_component_enabled( 'breadcrumbs' ) ) {
				$types[] = 'BreadcrumbList';
			}
		} elseif ( $object instanceof \WP_Term ) {
			if ( $this->has_valid_breadcrumb_structure( $object )
				&& SchemaArchitectureSettings::is_automatic_component_enabled( 'breadcrumbs' ) ) {
				$types[] = 'BreadcrumbList';
			}
		} elseif ( $object instanceof \WP_User ) {
			if ( ! in_array( 'Person', $types, true ) ) {
				$types[] = 'Person';
			}
			if ( $this->has_valid_breadcrumb_structure( $object )
				&& SchemaArchitectureSettings::is_automatic_component_enabled( 'breadcrumbs' ) ) {
				$types[] = 'BreadcrumbList';
			}
		}

		// Map BlogPosting/NewsArticle request onto Article type class for now.
		$normalized = array();
		foreach ( $types as $type ) {
			if ( in_array( $type, array( 'BlogPosting', 'NewsArticle' ), true ) ) {
				$normalized[] = 'Article';
			} else {
				$normalized[] = $type;
			}
		}

		return array_values( array_unique( $normalized ) );
	}

	/**
	 * Whether a rendered foundational node is structurally ready to emit.
	 *
	 * @param string $type  Schema type.
	 * @param array  $node  Rendered node.
	 * @return bool
	 */
	public function is_node_ready( $type, array $node ) {
		$result = SchemaReadinessEvaluator::evaluate( (string) $type, $node );
		return $result->is_ready();
	}

	/**
	 * Publisher identity mode.
	 *
	 * @return string organization|person|both
	 */
	public function publisher_identity() {
		$default  = SchemaArchitectureSettings::get_publisher_identity();
		$identity = apply_filters( 'seovaultseo_schema_v2_publisher_identity', $default );
		$identity = is_string( $identity ) ? strtolower( $identity ) : $default;
		if ( ! in_array( $identity, array( 'organization', 'person', 'both' ), true ) ) {
			return $default;
		}
		return $identity;
	}

	/**
	 * Article schema subtype label for emission (@type).
	 *
	 * @return string
	 */
	public function article_subtype() {
		$subtype = apply_filters( 'seovaultseo_schema_v2_article_type', 'Article' );
		$subtype = is_string( $subtype ) ? $subtype : 'Article';
		if ( ! in_array( $subtype, array( 'Article', 'BlogPosting', 'NewsArticle' ), true ) ) {
			return 'Article';
		}
		return $subtype;
	}

	/**
	 * Post types eligible for Article schema.
	 *
	 * @return string[]
	 */
	public function article_post_types() {
		$types = apply_filters( 'seovaultseo_schema_v2_article_post_types', array( 'post' ) );
		if ( ! is_array( $types ) ) {
			return array( 'post' );
		}
		$clean = array();
		foreach ( $types as $type ) {
			if ( is_string( $type ) && '' !== $type ) {
				$clean[] = sanitize_key( $type );
			}
		}
		return ! empty( $clean ) ? array_values( array_unique( $clean ) ) : array( 'post' );
	}

	/**
	 * Whether an article-eligible post needs an author Person entity.
	 *
	 * @param \WP_Post|\WP_Term|\WP_User|null $object Object.
	 * @return bool
	 */
	private function needs_author_person( $object ) {
		return $object instanceof \WP_Post && $this->is_article_eligible( $object );
	}

	/**
	 * Whether the post type is eligible for Article schema.
	 *
	 * @param \WP_Post $post Post.
	 * @return bool
	 */
	private function is_article_eligible( $post ) {
		if ( ! $post instanceof \WP_Post ) {
			return false;
		}
		return in_array( $post->post_type, $this->article_post_types(), true );
	}

	/**
	 * Whether the object/query is the site front page.
	 *
	 * @param \WP_Post|\WP_Term|\WP_User|null $object Object.
	 * @return bool
	 */
	private function is_front_context( $object ) {
		if ( is_front_page() ) {
			return true;
		}
		if ( $object instanceof \WP_Post ) {
			$page_on_front = (int) get_option( 'page_on_front' );
			if ( $page_on_front > 0 && (int) $object->ID === $page_on_front ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Whether a usable site-search target URL pattern exists.
	 *
	 * @return bool
	 */
	private function has_valid_search_target() {
		$target = apply_filters(
			'seovaultseo_schema_v2_search_target',
			home_url( '/?s={search_term_string}' )
		);
		if ( ! is_string( $target ) || '' === trim( $target ) ) {
			return false;
		}
		return false !== strpos( $target, '{search_term_string}' );
	}

	/**
	 * Whether a structurally ready breadcrumb trail can be built.
	 *
	 * @param \WP_Post|\WP_Term|\WP_User|null $object Object.
	 * @return bool
	 */
	private function has_valid_breadcrumb_structure( $object ) {
		if ( $this->is_front_context( $object ) ) {
			return false;
		}

		$renderer = new SchemaRenderer( $object );
		// Access breadcrumbs through a temporary BreadcrumbList field render.
		$fields = array(
			'itemListElement' => '%current.breadcrumbs%',
		);
		foreach ( $fields as &$value ) {
			$renderer->render( $value );
		}
		unset( $value );

		$trail = isset( $fields['itemListElement'] ) && is_array( $fields['itemListElement'] )
			? $fields['itemListElement']
			: array();

		$result = SchemaReadinessEvaluator::evaluate(
			'BreadcrumbList',
			array(
				'@type'           => 'BreadcrumbList',
				'itemListElement' => $trail,
			)
		);

		return $result->is_ready();
	}

	/**
	 * Adjust per-type field references for the active identity set.
	 *
	 * @param array    $data       Schema config.
	 * @param string   $type       Type.
	 * @param string[] $applicable Applicable types.
	 * @return array
	 */
	private function adjust_config_for_context( array $data, $type, array $applicable ) {
		if ( ! isset( $data['fields'] ) || ! is_array( $data['fields'] ) ) {
			$data['fields'] = array();
		}

		$identity = $this->publisher_identity();

		if ( 'WebSite' === $type ) {
			if ( ! in_array( 'SearchAction', $applicable, true ) ) {
				unset( $data['fields']['potentialAction'] );
			}
			if ( 'person' === $identity ) {
				$data['fields']['publisher'] = '%schemas.person%';
			} elseif ( 'organization' === $identity || 'both' === $identity ) {
				$data['fields']['publisher'] = '%schemas.organization%';
			}
			if ( 'person' === $identity && ! in_array( 'Person', $applicable, true ) ) {
				unset( $data['fields']['publisher'] );
			}
			if ( ( 'organization' === $identity || 'both' === $identity ) && ! in_array( 'Organization', $applicable, true ) ) {
				unset( $data['fields']['publisher'] );
			}
		}

		if ( 'Article' === $type ) {
			$subtype               = $this->article_subtype();
			$data['type']          = 'Article';
			$data['_v2_emit_type'] = $subtype;
			if ( ! in_array( 'Person', $applicable, true ) ) {
				unset( $data['fields']['author'] );
			}
			if ( 'person' === $identity ) {
				$data['fields']['publisher'] = '%schemas.person%';
			} elseif ( ! in_array( 'Organization', $applicable, true ) ) {
				unset( $data['fields']['publisher'] );
			}
		}

		return $data;
	}
}
