<?php
/**
 * V2 schema type-policy registry.
 *
 * Classifies supported schema types without changing emission. Catalogue
 * membership does not imply activation.
 *
 * @package SEOvaultSEO\Schema
 */

namespace SEOvaultSEO\Schema;

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

/**
 * Authoritative type classification for Schema Architecture V2.
 */
class SchemaTypePolicyRegistry {

	/**
	 * Allowed categories.
	 *
	 * @var string[]
	 */
	const CATEGORIES = array(
		'foundational',
		'block',
		'guided',
		'template',
		'integration',
		'custom',
	);

	/**
	 * Allowed editor methods.
	 *
	 * @var string[]
	 */
	const EDITOR_METHODS = array(
		'automatic',
		'block',
		'guided_form',
		'integration',
	);

	/**
	 * Allowed activation sources.
	 *
	 * @var string[]
	 */
	const ACTIVATION_SOURCES = array(
		'context',
		'block',
		'post_instance',
		'template',
		'integration',
		'custom',
		'extension',
	);

	/**
	 * Cached sanitized registry map keyed by type.
	 *
	 * @var array|null
	 */
	private static $cache = null;

	/**
	 * Reset static cache (tests only).
	 *
	 * @return void
	 */
	public static function reset_cache() {
		self::$cache = null;
	}

	/**
	 * Get the sanitized policy map keyed by canonical type.
	 *
	 * @return array<string,array>
	 */
	public static function get_entries() {
		if ( null !== self::$cache ) {
			return self::$cache;
		}

		$built_in = self::get_built_in_entries();
		$filtered = apply_filters( 'seovaultseo_schema_type_policy_registry', $built_in );

		self::$cache = self::sanitize_registry( $built_in, $filtered );
		return self::$cache;
	}

	/**
	 * Get one sanitized entry.
	 *
	 * @param string $type Canonical type.
	 * @return array|null
	 */
	public static function get_entry( $type ) {
		$type    = (string) $type;
		$entries = self::get_entries();
		return isset( $entries[ $type ] ) ? $entries[ $type ] : null;
	}

	/**
	 * Types whose registry default_enabled flag is true.
	 *
	 * Used by SchemaUtils as a compatibility adapter. Does not change emission
	 * by itself.
	 *
	 * @return string[]
	 */
	public static function get_default_enabled_types() {
		$types = array();
		foreach ( self::get_built_in_entries() as $type => $entry ) {
			if ( ! empty( $entry['default_enabled'] ) ) {
				$types[] = $type;
			}
		}
		return $types;
	}

	/**
	 * Whether a type is classified as foundational.
	 *
	 * @param string $type Type.
	 * @return bool
	 */
	public static function is_foundational( $type ) {
		$entry = self::get_entry( $type );
		return is_array( $entry ) && 'foundational' === ( $entry['category'] ?? '' );
	}

	/**
	 * Whether a type is a specialized (non-foundational) catalogue entry.
	 *
	 * @param string $type Type.
	 * @return bool
	 */
	public static function is_specialized( $type ) {
		$entry = self::get_entry( $type );
		if ( ! is_array( $entry ) ) {
			return false;
		}
		return 'foundational' !== ( $entry['category'] ?? '' );
	}

	/**
	 * Built-in registry definitions (unsanitized source of truth).
	 *
	 * @return array<string,array>
	 */
	public static function get_built_in_entries() {
		return array(
			'WebSite'             => self::entry(
				'WebSite',
				__( 'Website', 'seovault-ai' ),
				'foundational',
				array( 'context' ),
				'automatic',
				'website',
				'website',
				false,
				true
			),
			'WebPage'             => self::entry(
				'WebPage',
				__( 'Web page', 'seovault-ai' ),
				'foundational',
				array( 'context' ),
				'automatic',
				'web_page',
				'webpage',
				false,
				true
			),
			'Organization'        => self::entry(
				'Organization',
				__( 'Organization', 'seovault-ai' ),
				'foundational',
				array( 'context' ),
				'automatic',
				'organization',
				'organization',
				false,
				true
			),
			'BreadcrumbList'      => self::entry(
				'BreadcrumbList',
				__( 'Breadcrumbs', 'seovault-ai' ),
				'foundational',
				array( 'context' ),
				'automatic',
				'breadcrumb_list',
				'breadcrumb_list',
				false,
				true
			),
			'Article'             => self::entry(
				'Article',
				__( 'Article', 'seovault-ai' ),
				'foundational',
				array( 'context' ),
				'automatic',
				'article',
				'article',
				false,
				true
			),
			'SearchAction'        => self::entry(
				'SearchAction',
				__( 'Site search', 'seovault-ai' ),
				'foundational',
				array( 'context' ),
				'automatic',
				'search_action',
				'',
				false,
				true
			),
			'Person'              => self::entry(
				'Person',
				__( 'Person', 'seovault-ai' ),
				'foundational',
				array( 'context' ),
				'automatic',
				'person',
				'person',
				false,
				true
			),
			'Product'             => self::entry(
				'Product',
				__( 'Product', 'seovault-ai' ),
				'integration',
				array( 'integration', 'post_instance', 'template' ),
				'integration',
				'product',
				'product',
				true,
				false
			),
			'FAQPage'             => self::entry(
				'FAQPage',
				__( 'FAQ', 'seovault-ai' ),
				'block',
				array( 'block', 'template' ),
				'block',
				'faq_page',
				'faq_page',
				true,
				false
			),
			'HowTo'               => self::entry(
				'HowTo',
				__( 'How-to', 'seovault-ai' ),
				'block',
				array( 'block', 'template' ),
				'block',
				'how_to',
				'how_to',
				true,
				false
			),
			'Review'              => self::entry(
				'Review',
				__( 'Review', 'seovault-ai' ),
				'guided',
				array( 'post_instance', 'block', 'template' ),
				'guided_form',
				'review',
				'review',
				true,
				false
			),
			'Recipe'              => self::entry(
				'Recipe',
				__( 'Recipe', 'seovault-ai' ),
				'guided',
				array( 'post_instance', 'block', 'template' ),
				'guided_form',
				'recipe',
				'recipe',
				true,
				false
			),
			'Event'               => self::entry(
				'Event',
				__( 'Event', 'seovault-ai' ),
				'guided',
				array( 'post_instance', 'template', 'integration' ),
				'guided_form',
				'event',
				'event',
				true,
				false
			),
			'Course'              => self::entry(
				'Course',
				__( 'Course', 'seovault-ai' ),
				'guided',
				array( 'post_instance', 'template', 'integration' ),
				'guided_form',
				'course',
				'course',
				true,
				false
			),
			'SoftwareApplication' => self::entry(
				'SoftwareApplication',
				__( 'Software', 'seovault-ai' ),
				'guided',
				array( 'post_instance', 'template' ),
				'guided_form',
				'software_application',
				'software_application',
				true,
				false
			),
			'JobPosting'          => self::entry(
				'JobPosting',
				__( 'Job posting', 'seovault-ai' ),
				'guided',
				array( 'post_instance', 'template', 'integration' ),
				'guided_form',
				'job_posting',
				'job_posting',
				true,
				false
			),
		);
	}

	/**
	 * Build one registry entry array.
	 *
	 * @param string   $type                      Canonical type.
	 * @param string   $label                     User label.
	 * @param string   $category                  Category.
	 * @param string[] $allowed_sources           Activation sources.
	 * @param string   $editor_method             Editor method.
	 * @param string   $readiness_policy          Readiness policy key.
	 * @param string   $documentation_key         Google/docs metadata key.
	 * @param bool     $requires_visible_content  Visible-content parity flag.
	 * @param bool     $default_enabled           Compatibility default-enabled flag.
	 * @return array
	 */
	private static function entry( $type, $label, $category, array $allowed_sources, $editor_method, $readiness_policy, $documentation_key, $requires_visible_content, $default_enabled ) {
		return array(
			'type'                     => $type,
			'label'                    => $label,
			'category'                 => $category,
			'allowed_sources'          => $allowed_sources,
			'editor_method'            => $editor_method,
			'readiness_policy'         => $readiness_policy,
			'documentation_key'        => $documentation_key,
			'requires_visible_content' => (bool) $requires_visible_content,
			'default_enabled'          => (bool) $default_enabled,
		);
	}

	/**
	 * Sanitize a filtered registry while protecting built-in keys.
	 *
	 * @param array $built_in Built-in map.
	 * @param mixed $filtered Filter return value.
	 * @return array<string,array>
	 */
	public static function sanitize_registry( array $built_in, $filtered ) {
		$result = array();

		foreach ( $built_in as $type => $entry ) {
			$clean = self::sanitize_entry( $entry );
			if ( null !== $clean ) {
				$result[ $clean['type'] ] = $clean;
			}
		}

		if ( ! is_array( $filtered ) ) {
			return $result;
		}

		foreach ( $filtered as $type => $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}

			$clean = self::sanitize_entry( $entry );
			if ( null === $clean ) {
				continue;
			}

			// Built-in types cannot be replaced by extensions in Phase 2.
			if ( isset( $built_in[ $clean['type'] ] ) ) {
				continue;
			}

			// Duplicate keys in the filtered payload: first sanitized wins.
			if ( isset( $result[ $clean['type'] ] ) ) {
				continue;
			}

			$result[ $clean['type'] ] = $clean;
		}

		return $result;
	}

	/**
	 * Sanitize one registry entry.
	 *
	 * @param mixed $entry Raw entry.
	 * @return array|null
	 */
	public static function sanitize_entry( $entry ) {
		if ( ! is_array( $entry ) ) {
			return null;
		}

		$type = isset( $entry['type'] ) ? (string) $entry['type'] : '';
		if ( ! preg_match( '/^[A-Za-z][A-Za-z0-9]*$/', $type ) ) {
			return null;
		}

		$category = isset( $entry['category'] ) ? (string) $entry['category'] : '';
		if ( ! in_array( $category, self::CATEGORIES, true ) ) {
			return null;
		}

		$editor_method = isset( $entry['editor_method'] ) ? (string) $entry['editor_method'] : '';
		if ( ! in_array( $editor_method, self::EDITOR_METHODS, true ) ) {
			return null;
		}

		$sources = array();
		if ( isset( $entry['allowed_sources'] ) && is_array( $entry['allowed_sources'] ) ) {
			foreach ( $entry['allowed_sources'] as $source ) {
				if ( ! is_string( $source ) && ! is_numeric( $source ) ) {
					continue;
				}
				$source = (string) $source;
				if ( in_array( $source, self::ACTIVATION_SOURCES, true ) && ! in_array( $source, $sources, true ) ) {
					$sources[] = $source;
				}
			}
		}
		if ( empty( $sources ) ) {
			return null;
		}

		$label = isset( $entry['label'] ) ? sanitize_text_field( (string) $entry['label'] ) : $type;
		if ( '' === $label ) {
			$label = $type;
		}

		$readiness = isset( $entry['readiness_policy'] ) ? sanitize_key( (string) $entry['readiness_policy'] ) : '';
		if ( '' === $readiness ) {
			return null;
		}

		$documentation = isset( $entry['documentation_key'] ) ? sanitize_key( (string) $entry['documentation_key'] ) : '';

		return array(
			'type'                     => $type,
			'label'                    => $label,
			'category'                 => $category,
			'allowed_sources'          => $sources,
			'editor_method'            => $editor_method,
			'readiness_policy'         => $readiness,
			'documentation_key'        => $documentation,
			'requires_visible_content' => ! empty( $entry['requires_visible_content'] ),
			'default_enabled'          => ! empty( $entry['default_enabled'] ),
		);
	}
}
