<?php
/**
 * Schema Template
 *
 * Registers a custom post type for managing reusable schema templates
 * with display conditions. Phase 50 stores versioned V2 documents that
 * reuse guided field sanitizers and readiness before publish/emission.
 *
 * @package SEOvaultSEO\Schema
 */

namespace SEOvaultSEO\Schema;

use SEOvaultSEO\Schema\Readiness\SchemaReadinessEvaluator;
use SEOvaultSEO\Schema\Validation\SchemaValidationCache;
use SEOvaultSEO\Utils\Logger;
use SEOvaultSEO\Utils\Sanitizer;

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

class SchemaTemplate {

	/**
	 * Custom post type slug.
	 */
	const POST_TYPE = 'seovault_schema';

	/**
	 * Meta key for stored schema data (versioned JSON document).
	 */
	const META_KEY = '_seovault_schema_data';

	/**
	 * Document schema version.
	 */
	const DOCUMENT_VERSION = 2;

	/**
	 * Guard against recursive demote-to-draft saves.
	 *
	 * @var bool
	 */
	private static $demoting = false;

	/**
	 * Initialize hooks.
	 */
	public static function init() {
		add_action( 'init', array( __CLASS__, 'register_post_type' ) );
		add_action( 'save_post_' . self::POST_TYPE, array( __CLASS__, 'save_template' ), 10, 2 );
		add_action( 'edit_form_after_title', array( __CLASS__, 'render_editor' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_admin_assets' ) );
		add_action( 'wp_ajax_seovaultseo_schema_fetch_url', array( __CLASS__, 'ajax_fetch_from_url' ) );
		add_action( 'wp_ajax_seovaultseo_schema_list_templates', array( __CLASS__, 'ajax_list_templates' ) );
		add_action( 'wp_ajax_seovaultseo_schema_template_estimate_scope', array( __CLASS__, 'ajax_estimate_scope' ) );
		add_action( 'wp_ajax_seovaultseo_schema_template_preview', array( __CLASS__, 'ajax_preview_on_post' ) );
		add_action( 'wp_ajax_seovaultseo_schema_template_search_posts', array( __CLASS__, 'ajax_search_posts' ) );
	}

	/**
	 * Register the schema template custom post type.
	 */
	public static function register_post_type() {
		$labels = array(
			'name'               => __( 'Schema Templates', 'seovault-ai' ),
			'singular_name'      => __( 'Schema Template', 'seovault-ai' ),
			'menu_name'          => __( 'Schema Templates', 'seovault-ai' ),
			'all_items'          => __( 'All Schema Templates', 'seovault-ai' ),
			'add_new'            => __( 'Add New Template', 'seovault-ai' ),
			'add_new_item'       => __( 'Add New Schema Template', 'seovault-ai' ),
			'new_item'           => __( 'New Schema Template', 'seovault-ai' ),
			'edit_item'          => __( 'Edit Schema Template', 'seovault-ai' ),
			'view_item'          => __( 'View Schema Template', 'seovault-ai' ),
			'search_items'       => __( 'Search Schema Templates', 'seovault-ai' ),
			'not_found'          => __( 'No schema templates found.', 'seovault-ai' ),
			'not_found_in_trash' => __( 'No schema templates found in Trash.', 'seovault-ai' ),
		);

		$args = array(
			'labels'              => $labels,
			'description'         => __( 'Reusable schema templates with display conditions.', 'seovault-ai' ),
			'supports'            => array( 'title' ),
			'hierarchical'        => false,
			'public'              => false,
			'show_ui'             => true,
			'show_in_menu'        => false,
			'show_in_admin_bar'   => false,
			'show_in_nav_menus'   => false,
			'can_export'          => true,
			'has_archive'         => false,
			'exclude_from_search' => true,
			'publicly_queryable'  => false,
			'rewrite'             => false,
			'capability_type'     => 'page',
			'show_in_rest'        => false,
		);

		register_post_type( self::POST_TYPE, $args );
	}

	/**
	 * AJAX: return a list of schema templates as HTML.
	 */
	public static function ajax_list_templates() {
		check_ajax_referer( 'seovaultseo_admin', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			Logger::error( 'SchemaTemplate::ajax_list_templates: capability check failed' );
			wp_send_json_error( array( 'message' => __( 'Unauthorized.', 'seovault-ai' ) ), 403 );
		}

		$templates   = self::get_all_templates();
		$add_new_url = admin_url( 'post-new.php?post_type=' . self::POST_TYPE );

		Logger::log( 'SchemaTemplate::ajax_list_templates: loaded templates', array( 'count' => count( $templates ) ) );

		$buffer_level = ob_get_level();
		ob_start();
		$html = '';
		try {
			?>
		<div class="seovaultseo-schema-templates-list">
			<p>
				<a href="<?php echo esc_url( $add_new_url ); ?>" class="button button-primary">
					<span class="dashicons dashicons-plus-alt2"></span>
					<?php esc_html_e( 'Add New Template', 'seovault-ai' ); ?>
				</a>
			</p>
			<?php if ( empty( $templates ) ) : ?>
				<p class="description"><?php esc_html_e( 'No schema templates found. Click "Add New Template" to create one.', 'seovault-ai' ); ?></p>
			<?php else : ?>
				<table class="widefat striped">
					<thead>
						<tr>
							<th><?php esc_html_e( 'Title', 'seovault-ai' ); ?></th>
							<th><?php esc_html_e( 'Schema Type', 'seovault-ai' ); ?></th>
							<th><?php esc_html_e( 'Actions', 'seovault-ai' ); ?></th>
						</tr>
					</thead>
					<tbody>
						<?php foreach ( $templates as $template ) : ?>
							<?php
							$edit_url    = admin_url( 'post.php?post=' . $template['id'] . '&action=edit' );
							$delete_url  = admin_url( 'post.php?post=' . $template['id'] . '&action=trash' );
							$schema_type = $template['schemaType'] ?? $template['type'] ?? '—';
							?>
							<tr>
								<td>
									<strong>
										<a href="<?php echo esc_url( $edit_url ); ?>"><?php echo esc_html( $template['title'] ); ?></a>
									</strong>
								</td>
								<td><code><?php echo esc_html( $schema_type ); ?></code></td>
								<td>
									<a href="<?php echo esc_url( $edit_url ); ?>" class="button button-small">
										<span class="dashicons dashicons-edit"></span>
										<?php esc_html_e( 'Edit', 'seovault-ai' ); ?>
									</a>
									<a href="<?php echo esc_url( $delete_url ); ?>" class="button button-small">
										<span class="dashicons dashicons-trash"></span>
										<?php esc_html_e( 'Trash', 'seovault-ai' ); ?>
									</a>
								</td>
							</tr>
						<?php endforeach; ?>
					</tbody>
				</table>
			<?php endif; ?>
		</div>
			<?php
			$html = (string) ob_get_clean();
		} finally {
			while ( ob_get_level() > $buffer_level ) {
				ob_end_clean();
			}
		}

		wp_send_json_success( array( 'html' => $html ) );
	}

	/**
	 * Save schema template data when the post is saved.
	 *
	 * @param int      $post_id Post ID.
	 * @param \WP_Post $post    Post object.
	 */
	public static function save_template( $post_id, $post ) {
		if ( self::$demoting ) {
			return;
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		if ( ! isset( $_POST['seovault_schema_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['seovault_schema_nonce'] ) ), 'seovault_schema_template' ) ) {
			return;
		}

		if ( ! isset( $_POST['seovault_schema_data'] ) || ! is_string( $_POST['seovault_schema_data'] ) ) {
			return;
		}

		$raw  = wp_strip_all_tags( wp_unslash( $_POST['seovault_schema_data'] ), true );
		$data = json_decode( $raw, true );

		if ( ! is_array( $data ) ) {
			Logger::error( 'SchemaTemplate::save_template: invalid JSON data', array( 'post_id' => $post_id ) );
			return;
		}

		$data     = Sanitizer::sanitize_schema_data( $data );
		$document = self::build_document_from_raw( $data, (int) $post_id, (string) $post->post_title );

		// Prefer structured condition fields from the Phase 51 UI when present.
		if ( isset( $_POST['seovault_display_conditions'] ) && is_array( $_POST['seovault_display_conditions'] ) ) {
			$raw_conditions = map_deep( wp_unslash( $_POST['seovault_display_conditions'] ), 'sanitize_text_field' );
			$sanitized_dc   = DisplayConditionsSanitizer::sanitize( $raw_conditions );
			$document['metadata']['displayConditions'] = $sanitized_dc['conditions'];
		}

		if ( isset( $_POST['seovault_schema_type'] ) ) {
			$posted_type = sanitize_text_field( wp_unslash( (string) $_POST['seovault_schema_type'] ) );
			if ( '' !== $posted_type && isset( self::get_template_builder_types()[ $posted_type ] ) ) {
				$document['type'] = $posted_type;
			}
		}

		$targeting = DisplayConditionsSanitizer::validate_specialized_targeting(
			(string) ( $document['type'] ?? '' ),
			isset( $document['metadata']['displayConditions'] ) && is_array( $document['metadata']['displayConditions'] )
				? $document['metadata']['displayConditions']
				: array()
		);

		self::write_document( (int) $post_id, $document );

		$should_demote = false;
		if ( 'publish' === $post->post_status && ! self::is_publishable_document( $document ) ) {
			$should_demote = true;
		}
		if ( 'publish' === $post->post_status && empty( $targeting['ok'] ) ) {
			$should_demote = true;
			update_post_meta( (int) $post_id, '_seovault_schema_template_targeting_errors', $targeting['errors'] );
		} else {
			delete_post_meta( (int) $post_id, '_seovault_schema_template_targeting_errors' );
		}

		if ( $should_demote ) {
			self::demote_to_draft( (int) $post_id );
			Logger::log(
				'SchemaTemplate::save_template: demoted template to draft',
				array(
					'post_id'   => (int) $post_id,
					'type'      => $document['type'] ?? 'unknown',
					'targeting' => $targeting['errors'],
				)
			);
		}

		Logger::log(
			'SchemaTemplate::save_template: template saved',
			array(
				'post_id' => (int) $post_id,
				'type'    => $document['type'] ?? 'unknown',
				'version' => self::DOCUMENT_VERSION,
			)
		);
	}

	/**
	 * Build a versioned template document from raw editor/migration input.
	 *
	 * @param array  $raw        Raw payload.
	 * @param int    $post_id    Template post ID.
	 * @param string $post_title Post title.
	 * @return array
	 */
	public static function build_document_from_raw( array $raw, $post_id, $post_title = '' ) {
		$post_id    = (int) $post_id;
		$post_title = is_string( $post_title ) ? $post_title : '';
		$type       = isset( $raw['type'] ) ? sanitize_text_field( (string) $raw['type'] ) : 'Thing';
		$fields_raw = isset( $raw['fields'] ) && is_array( $raw['fields'] ) ? $raw['fields'] : array();

		if ( class_exists( SchemaMigration::class ) ) {
			$fields_raw = SchemaMigration::extract_field_values( $fields_raw );
		}

		if ( SchemaInstanceFieldSanitizer::is_guided_or_manual_product_type( $type ) ) {
			$sanitized_fields = SchemaInstanceFieldSanitizer::sanitize( $type, $fields_raw );
			$fields           = isset( $sanitized_fields['fields'] ) && is_array( $sanitized_fields['fields'] )
				? $sanitized_fields['fields']
				: array();
			$enabled = array_key_exists( 'enabled', $raw ) ? (bool) $raw['enabled'] : true;
		} else {
			$legacy  = SchemaSanitizer::sanitize_schema( $raw );
			$type    = (string) ( $legacy['type'] ?? $type );
			$fields  = isset( $legacy['fields'] ) && is_array( $legacy['fields'] ) ? $legacy['fields'] : array();
			$enabled = array_key_exists( 'enabled', $legacy ) ? (bool) $legacy['enabled'] : true;
			$raw     = array_merge( $raw, $legacy );
		}

		$metadata = isset( $raw['metadata'] ) && is_array( $raw['metadata'] ) ? $raw['metadata'] : array();
		$migration_meta = isset( $metadata['migration'] ) && is_array( $metadata['migration'] ) ? $metadata['migration'] : null;
		$incoming_source_id = isset( $metadata['source_id'] ) && is_string( $metadata['source_id'] )
			? sanitize_text_field( $metadata['source_id'] )
			: '';
		if ( method_exists( SchemaSanitizer::class, 'sanitize_metadata' ) ) {
			$metadata = SchemaSanitizer::sanitize_metadata( $metadata );
		}
		if ( ! is_array( $metadata ) ) {
			$metadata = array();
		}
		if ( '' !== $post_title ) {
			$metadata['title'] = sanitize_text_field( $post_title );
		} elseif ( empty( $metadata['title'] ) && ! empty( $raw['title'] ) ) {
			$metadata['title'] = sanitize_text_field( (string) $raw['title'] );
		}

		if ( null !== $migration_meta ) {
			$metadata['migration'] = array(
				'source_id'      => sanitize_text_field( (string) ( $migration_meta['source_id'] ?? '' ) ),
				'migration_id'   => sanitize_text_field( (string) ( $migration_meta['migration_id'] ?? '' ) ),
				'classification' => sanitize_text_field( (string) ( $migration_meta['classification'] ?? '' ) ),
				'from'           => sanitize_text_field( (string) ( $migration_meta['from'] ?? '' ) ),
			);
		}

		$source_id = self::stable_source_id( $post_id );
		if ( '' === $source_id && '' !== $incoming_source_id ) {
			$source_id = $incoming_source_id;
		}
		if ( '' !== $source_id ) {
			$metadata['source_id'] = $source_id;
		}

		$document = array(
			'version'  => self::DOCUMENT_VERSION,
			'title'    => isset( $metadata['title'] ) ? (string) $metadata['title'] : $post_title,
			'type'     => $type,
			'enabled'  => $enabled,
			'fields'   => $fields,
			'metadata' => $metadata,
		);

		if ( isset( $raw['show_on'] ) && is_array( $raw['show_on'] ) ) {
			$document['show_on'] = SchemaSanitizer::sanitize_show_on( $raw['show_on'] );
		}
		if ( isset( $raw['not_show_on'] ) && is_array( $raw['not_show_on'] ) ) {
			$document['not_show_on'] = SchemaSanitizer::sanitize_show_on( $raw['not_show_on'] );
		}

		return $document;
	}

	/**
	 * Persist a versioned template document.
	 *
	 * @param int   $post_id  Template post ID.
	 * @param array $document Document.
	 * @return bool
	 */
	public static function write_document( $post_id, array $document ) {
		$post_id = (int) $post_id;
		if ( $post_id <= 0 ) {
			return false;
		}
		$document['version'] = self::DOCUMENT_VERSION;
		update_post_meta( $post_id, self::META_KEY, wp_json_encode( $document ) );
		return true;
	}

	/**
	 * Read and normalize a template document.
	 *
	 * @param int $post_id Template post ID.
	 * @return array|null
	 */
	public static function get_document( $post_id ) {
		$raw = self::get_template( $post_id );
		if ( null === $raw ) {
			return null;
		}
		return self::normalize_document( $raw, (int) $post_id );
	}

	/**
	 * Normalize legacy or V2 template payloads into the versioned shape.
	 *
	 * @param array $raw     Raw stored payload.
	 * @param int   $post_id Template post ID (0 when anonymous).
	 * @return array
	 */
	public static function normalize_document( array $raw, $post_id = 0 ) {
		$post_id = (int) $post_id;
		$version = isset( $raw['version'] ) ? (int) $raw['version'] : 1;
		$type    = isset( $raw['type'] ) ? sanitize_text_field( (string) $raw['type'] ) : 'Thing';
		$fields  = isset( $raw['fields'] ) && is_array( $raw['fields'] ) ? $raw['fields'] : array();
		if ( empty( $fields ) ) {
			$fields = SchemaUtils::parse_fields( $raw );
		}
		if ( class_exists( SchemaMigration::class ) ) {
			$fields = SchemaMigration::extract_field_values( $fields );
		}

		$metadata = isset( $raw['metadata'] ) && is_array( $raw['metadata'] ) ? $raw['metadata'] : array();
		if ( $post_id > 0 ) {
			$source_id = self::stable_source_id( $post_id );
			if ( '' !== $source_id ) {
				$metadata['source_id'] = $source_id;
			}
		} elseif ( empty( $metadata['source_id'] ) ) {
			$metadata['source_id'] = '';
		}

		$document = array(
			'version'  => $version >= self::DOCUMENT_VERSION ? self::DOCUMENT_VERSION : $version,
			'title'    => isset( $raw['title'] ) ? (string) $raw['title'] : (string) ( $metadata['title'] ?? '' ),
			'type'     => $type,
			'enabled'  => array_key_exists( 'enabled', $raw ) ? (bool) $raw['enabled'] : true,
			'fields'   => $fields,
			'metadata' => $metadata,
		);

		if ( isset( $raw['show_on'] ) && is_array( $raw['show_on'] ) ) {
			$document['show_on'] = $raw['show_on'];
		}
		if ( isset( $raw['not_show_on'] ) && is_array( $raw['not_show_on'] ) ) {
			$document['not_show_on'] = $raw['not_show_on'];
		}

		return $document;
	}

	/**
	 * Stable source id for diagnostics / cache identity.
	 *
	 * Prefers migration IDs when present so reruns stay idempotent.
	 *
	 * @param int $post_id Template post ID.
	 * @return string
	 */
	public static function stable_source_id( $post_id ) {
		$post_id = (int) $post_id;
		if ( $post_id <= 0 ) {
			return '';
		}

		if ( class_exists( SchemaMigration::class ) ) {
			$migration_id = get_post_meta( $post_id, SchemaMigration::META_MIGRATION_ID, true );
			if ( is_string( $migration_id ) && '' !== $migration_id ) {
				return sanitize_text_field( $migration_id );
			}
		}

		$raw = get_post_meta( $post_id, self::META_KEY, true );
		if ( is_string( $raw ) && '' !== $raw ) {
			$decoded = json_decode( $raw, true );
			if ( is_array( $decoded ) && ! empty( $decoded['metadata']['source_id'] ) && is_string( $decoded['metadata']['source_id'] ) ) {
				return sanitize_text_field( $decoded['metadata']['source_id'] );
			}
		}

		return 'template:' . $post_id;
	}

	/**
	 * Stable source_ref for emission descriptors.
	 *
	 * @param int $post_id Template post ID.
	 * @return string
	 */
	public static function stable_source_ref( $post_id ) {
		$post_id = (int) $post_id;
		if ( $post_id <= 0 ) {
			return '';
		}
		return 'template:' . $post_id;
	}

	/**
	 * Whether a specialized template document may remain published.
	 *
	 * Mapped %variables% are treated as provisionally filled so templates can
	 * publish before page-context resolution. Emission still re-evaluates after
	 * resolving variables against the target page.
	 *
	 * @param array $document Template document.
	 * @return bool
	 */
	public static function is_publishable_document( array $document ) {
		$type   = (string) ( $document['type'] ?? '' );
		$fields = isset( $document['fields'] ) && is_array( $document['fields'] ) ? $document['fields'] : array();
		if ( '' === $type ) {
			return false;
		}
		if ( ! SchemaTypePolicyRegistry::is_specialized( $type ) ) {
			return true;
		}
		$provisional = self::with_provisional_variables( $fields );
		$result      = SchemaReadinessEvaluator::evaluate( $type, $provisional );
		return $result->is_ready();
	}

	/**
	 * Replace pure mapped %variable% tokens with a provisional meaningful sentinel.
	 *
	 * @param mixed $value Field tree.
	 * @return mixed
	 */
	public static function with_provisional_variables( $value ) {
		if ( is_string( $value ) ) {
			$trimmed = trim( $value );
			if ( preg_match( '/^%[a-z0-9_.]+%$/i', $trimmed ) ) {
				return '__seovault_provisional__';
			}
			return $value;
		}
		if ( ! is_array( $value ) ) {
			return $value;
		}
		$out = array();
		foreach ( $value as $key => $item ) {
			$out[ $key ] = self::with_provisional_variables( $item );
		}
		return $out;
	}

	/**
	 * Demote a published template to draft and bump cache generation.
	 *
	 * @param int $post_id Template post ID.
	 * @return void
	 */
	public static function demote_to_draft( $post_id ) {
		$post_id = (int) $post_id;
		if ( $post_id <= 0 || self::$demoting ) {
			return;
		}
		self::$demoting = true;
		wp_update_post(
			array(
				'ID'          => $post_id,
				'post_status' => 'draft',
			)
		);
		self::$demoting = false;
		SchemaValidationCache::bump_generation();
	}

	/**
	 * Get a single schema template by post ID.
	 *
	 * @param int $post_id Template post ID.
	 * @return array|null Schema data or null.
	 */
	public static function get_template( $post_id ) {
		$post_id = absint( $post_id );
		if ( ! $post_id ) {
			return null;
		}

		$data = get_post_meta( $post_id, self::META_KEY, true );
		if ( empty( $data ) ) {
			return null;
		}

		// Data is stored as JSON string — decode it.
		if ( is_string( $data ) ) {
			$decoded = json_decode( $data, true );
			if ( ! is_array( $decoded ) ) {
				return null;
			}
			return $decoded;
		}

		// Backward compat: if stored as array (older versions), return as-is.
		if ( is_array( $data ) ) {
			return $data;
		}

		return null;
	}

	/**
	 * Get all published schema templates.
	 *
	 * @return array Array of template objects with schema data.
	 */
	public static function get_all_templates() {
		$posts = get_posts(
			array(
				'post_type'      => self::POST_TYPE,
				'post_status'    => 'publish',
				'posts_per_page' => -1,
				'orderby'        => 'title',
				'order'          => 'ASC',
			)
		);

		if ( empty( $posts ) ) {
			return array();
		}

		$templates = array();
		foreach ( $posts as $post ) {
			$data = self::get_template( $post->ID );
			if ( null === $data ) {
				continue;
			}
			$data['id']    = $post->ID;
			$data['title'] = $post->post_title;
			$templates[]   = $data;
		}

		return $templates;
	}

	/**
	 * AJAX: fetch schema from a URL using the parser.
	 */
	public static function ajax_fetch_from_url() {
		check_ajax_referer( 'seovaultseo_admin', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			Logger::error( 'SchemaTemplate::ajax_fetch_from_url: capability check failed' );
			wp_send_json_error( array( 'message' => __( 'Unauthorized.', 'seovault-ai' ) ), 403 );
		}

		$url = isset( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : '';
		if ( empty( $url ) ) {
			wp_send_json_error( array( 'message' => __( 'No URL provided.', 'seovault-ai' ) ) );
		}

		Logger::log( 'SchemaTemplate::ajax_fetch_from_url: fetching', array( 'url' => $url ) );

		$parser  = new SchemaParser();
		$results = $parser->from_url( $url );

		if ( is_wp_error( $results ) ) {
			Logger::error(
				'SchemaTemplate::ajax_fetch_from_url: parser error',
				array(
					'url'   => $url,
					'error' => $results->get_error_message(),
				)
			);
			wp_send_json_error( array( 'message' => $results->get_error_message() ) );
		}

		Logger::log(
			'SchemaTemplate::ajax_fetch_from_url: success',
			array(
				'url'     => $url,
				'schemas' => is_array( $results ) ? count( $results ) : 0,
			)
		);
		wp_send_json_success( array( 'schemas' => $results ) );
	}

	/**
	 * Types allowed in the specialized template builder (excludes foundational).
	 *
	 * @return array<string,string> type => label
	 */
	public static function get_template_builder_types() {
		$out = array();
		foreach ( SchemaTypePolicyRegistry::get_entries() as $type => $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}
			if ( SchemaTypePolicyRegistry::is_foundational( $type ) ) {
				continue;
			}
			$sources = isset( $entry['allowed_sources'] ) && is_array( $entry['allowed_sources'] )
				? $entry['allowed_sources']
				: array();
			if ( ! in_array( 'template', $sources, true ) ) {
				continue;
			}
			$out[ $type ] = isset( $entry['label'] ) ? (string) $entry['label'] : (string) $type;
		}
		return $out;
	}

	/**
	 * Enqueue template editor assets on the CPT screens.
	 *
	 * @param string $hook Hook.
	 * @return void
	 */
	public static function enqueue_admin_assets( $hook ) {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( ! $screen || self::POST_TYPE !== $screen->post_type ) {
			return;
		}
		if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
			return;
		}

		\SEOvaultSEO\Extension\Assets::register();
		wp_enqueue_style( \SEOvaultSEO\Extension\ExtensionApi::HANDLE_ADMIN_FOUNDATION );

		wp_enqueue_script(
			'seovaultseo-schema-template-admin',
			SEOVAULT_SEO_URL . 'assets/js/schema-template-admin.js',
			array( 'jquery' ),
			SEOVAULT_SEO_VERSION . '-phase51',
			true
		);

		wp_localize_script(
			'seovaultseo-schema-template-admin',
			'seovaultSchemaTemplateAdmin',
			array(
				'ajaxUrl' => admin_url( 'admin-ajax.php' ),
				'nonce'   => wp_create_nonce( 'seovaultseo_admin' ),
				'i18n'    => array(
					'addCondition'   => __( 'Add condition', 'seovault-ai' ),
					'remove'         => __( 'Remove', 'seovault-ai' ),
					'estimating'     => __( 'Estimating scope…', 'seovault-ai' ),
					'previewing'     => __( 'Previewing…', 'seovault-ai' ),
					'broadBlocked'   => __( 'Specialized templates cannot target the entire site or all posts/pages. Choose a post type, taxonomy, or specific content.', 'seovault-ai' ),
					'previewMatch'   => __( 'Conditions match this post.', 'seovault-ai' ),
					'previewNoMatch' => __( 'Conditions do not match this post.', 'seovault-ai' ),
					'ready'          => __( 'Ready to emit', 'seovault-ai' ),
					'notReady'       => __( 'Not ready — would be withheld', 'seovault-ai' ),
					'include'        => __( 'Include', 'seovault-ai' ),
					// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- i18n label key, not get_posts() exclude.
					'exclude'        => __( 'Exclude', 'seovault-ai' ),
				),
				'postTypes' => self::get_public_post_type_choices(),
				'taxonomies'=> self::get_public_taxonomy_choices(),
			)
		);
	}

	/**
	 * Public post type choices for condition controls.
	 *
	 * @return array<int,array{value:string,label:string}>
	 */
	public static function get_public_post_type_choices() {
		$choices = array(
			array(
				'value' => 'all',
				'label' => __( 'All singular content', 'seovault-ai' ),
			),
		);
		foreach ( get_post_types( array( 'public' => true ), 'objects' ) as $pt ) {
			if ( self::POST_TYPE === $pt->name ) {
				continue;
			}
			$choices[] = array(
				'value' => $pt->name,
				'label' => $pt->labels->singular_name ? $pt->labels->singular_name : $pt->name,
			);
		}
		return $choices;
	}

	/**
	 * Public taxonomy choices.
	 *
	 * @return array<int,array{value:string,label:string}>
	 */
	public static function get_public_taxonomy_choices() {
		$choices = array();
		foreach ( get_taxonomies( array( 'public' => true ), 'objects' ) as $tax ) {
			$choices[] = array(
				'value' => $tax->name,
				'label' => $tax->labels->singular_name ? $tax->labels->singular_name : $tax->name,
			);
		}
		return $choices;
	}

	/**
	 * AJAX: estimate matched scope for posted conditions.
	 */
	public static function ajax_estimate_scope() {
		check_ajax_referer( 'seovaultseo_admin', 'nonce' );
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized.', 'seovault-ai' ) ), 403 );
		}

		$raw = array();
		if ( isset( $_POST['conditions'] ) ) {
			$raw = is_array( $_POST['conditions'] )
				? map_deep( wp_unslash( $_POST['conditions'] ), 'sanitize_text_field' )
				: wp_strip_all_tags( wp_unslash( (string) $_POST['conditions'] ), true );
		}
		if ( is_string( $raw ) ) {
			$decoded = json_decode( $raw, true );
			$raw     = is_array( $decoded ) ? Sanitizer::sanitize_schema_data( $decoded ) : array();
		}
		$sanitized = DisplayConditionsSanitizer::sanitize( $raw );
		$scope     = DisplayConditionsSanitizer::estimate_scope( $sanitized['conditions'] );
		$type      = isset( $_POST['schema_type'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['schema_type'] ) ) : '';
		$targeting = DisplayConditionsSanitizer::validate_specialized_targeting( $type, $sanitized['conditions'] );

		wp_send_json_success(
			array(
				'scope'     => $scope,
				'targeting' => $targeting,
				'conditions'=> $sanitized['conditions'],
			)
		);
	}

	/**
	 * AJAX: preview template against a selected post.
	 */
	public static function ajax_preview_on_post() {
		check_ajax_referer( 'seovaultseo_admin', 'nonce' );
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized.', 'seovault-ai' ) ), 403 );
		}

		$preview_id = isset( $_POST['preview_post_id'] ) ? absint( $_POST['preview_post_id'] ) : 0;
		$post       = $preview_id ? get_post( $preview_id ) : null;
		if ( ! $post instanceof \WP_Post ) {
			wp_send_json_error( array( 'message' => __( 'Select a valid post to preview.', 'seovault-ai' ) ) );
		}
		if ( ! current_user_can( 'edit_post', $preview_id ) && 'publish' !== $post->post_status ) {
			wp_send_json_error( array( 'message' => __( 'You cannot preview against this post.', 'seovault-ai' ) ), 403 );
		}

		$template_id = isset( $_POST['template_id'] ) ? absint( $_POST['template_id'] ) : 0;
		$document    = null;
		if ( $template_id > 0 ) {
			$document = self::get_document( $template_id );
		}
		if ( ! is_array( $document ) ) {
			$document = array(
				'type'     => isset( $_POST['schema_type'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['schema_type'] ) ) : '',
				'fields'   => array(),
				'metadata' => array(),
			);
			if ( isset( $_POST['fields'] ) ) {
				$fields_raw = is_array( $_POST['fields'] )
					? map_deep( wp_unslash( $_POST['fields'] ), 'sanitize_text_field' )
					: wp_strip_all_tags( wp_unslash( (string) $_POST['fields'] ), true );
				if ( is_string( $fields_raw ) ) {
					$decoded    = json_decode( $fields_raw, true );
					$fields_raw = is_array( $decoded ) ? Sanitizer::sanitize_schema_data( $decoded ) : array();
				}
				if ( is_array( $fields_raw ) ) {
					$document['fields'] = $fields_raw;
				}
			}
		}

		$raw_conditions = null;
		if ( isset( $_POST['conditions'] ) ) {
			$raw_conditions = is_array( $_POST['conditions'] )
				? map_deep( wp_unslash( $_POST['conditions'] ), 'sanitize_text_field' )
				: wp_strip_all_tags( wp_unslash( (string) $_POST['conditions'] ), true );
		}
		if ( null !== $raw_conditions ) {
			if ( is_string( $raw_conditions ) ) {
				$decoded        = json_decode( $raw_conditions, true );
				$raw_conditions = is_array( $decoded ) ? Sanitizer::sanitize_schema_data( $decoded ) : array();
			}
			$sanitized = DisplayConditionsSanitizer::sanitize( $raw_conditions );
			$document['metadata']['displayConditions'] = $sanitized['conditions'];
		}

		$result = self::preview_document_on_post( $document, $post );
		wp_send_json_success( $result );
	}

	/**
	 * AJAX: search posts for preview / specific-content targeting.
	 */
	public static function ajax_search_posts() {
		check_ajax_referer( 'seovaultseo_admin', 'nonce' );
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized.', 'seovault-ai' ) ), 403 );
		}

		$term      = isset( $_POST['term'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['term'] ) ) : '';
		$post_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( (string) $_POST['post_type'] ) ) : 'any';
		if ( '' === $post_type || 'all' === $post_type ) {
			$post_type = 'any';
		}

		$query = new \WP_Query(
			array(
				's'                      => $term,
				'post_type'              => $post_type,
				'post_status'            => array( 'publish', 'private', 'draft' ),
				'posts_per_page'         => 20,
				'orderby'                => 'relevance',
				'order'                  => 'DESC',
				'no_found_rows'          => true,
				'update_post_meta_cache' => false,
				'update_post_term_cache' => false,
			)
		);

		$results = array();
		foreach ( $query->posts as $post ) {
			if ( ! $post instanceof \WP_Post ) {
				continue;
			}
			$results[] = array(
				'id'    => (int) $post->ID,
				'title' => get_the_title( $post ),
				'type'  => $post->post_type,
				'status'=> $post->post_status,
			);
		}

		wp_send_json_success( array( 'results' => $results ) );
	}

	/**
	 * Preview a template document against a post (conditions + readiness).
	 *
	 * @param array    $document Template document.
	 * @param \WP_Post $post     Preview post.
	 * @return array
	 */
	public static function preview_document_on_post( array $document, \WP_Post $post ) {
		$type       = (string) ( $document['type'] ?? '' );
		$conditions = isset( $document['metadata']['displayConditions'] ) && is_array( $document['metadata']['displayConditions'] )
			? $document['metadata']['displayConditions']
			: array();
		$sanitized  = DisplayConditionsSanitizer::sanitize( $conditions );
		$conditions = $sanitized['conditions'];
		$document['metadata']['displayConditions'] = $conditions;

		$matches   = DisplayConditions::evaluate( $document, $post );
		$targeting = DisplayConditionsSanitizer::validate_specialized_targeting( $type, $conditions );
		$scope     = DisplayConditionsSanitizer::estimate_scope( $conditions );

		$fields = isset( $document['fields'] ) && is_array( $document['fields'] ) ? $document['fields'] : array();
		if ( SchemaInstanceFieldSanitizer::is_guided_or_manual_product_type( $type ) ) {
			$san_fields = SchemaInstanceFieldSanitizer::sanitize( $type, $fields );
			$fields     = $san_fields['fields'];
		} else {
			$fields = SchemaSanitizer::sanitize_fields( $fields );
			$fields = SchemaSanitizer::normalize_datetime_fields( $fields, $type );
		}
		$renderer = new SchemaRenderer( $post );
		$resolved = SchemaTemplateCollector::resolve_fields( $fields, $renderer );

		$readiness  = null;
		$would_emit = false;
		if ( '' !== $type && SchemaTypePolicyRegistry::is_specialized( $type ) ) {
			$readiness  = SchemaReadinessEvaluator::evaluate( $type, $resolved );
			$unresolved = SchemaTemplateCollector::has_unresolved_mapped_variables( $resolved );
			$would_emit = $matches && $targeting['ok'] && $readiness->is_ready() && ! $unresolved;
		} else {
			$would_emit = $matches && $targeting['ok'];
		}

		return array(
			'matches'     => $matches,
			'would_emit'  => $would_emit,
			'targeting'   => $targeting,
			'scope'       => $scope,
			'readiness'   => $readiness ? $readiness->to_array() : null,
			'resolved'    => $resolved,
			'preview_post'=> array(
				'id'    => (int) $post->ID,
				'title' => get_the_title( $post ),
				'type'  => $post->post_type,
			),
		);
	}

	/**
	 * Render the schema template editor metabox.
	 * Called on edit_form_after_title for the custom post type.
	 */
	public static function render_editor() {
		$screen = get_current_screen();
		if ( ! $screen || self::POST_TYPE !== $screen->post_type ) {
			return;
		}

		wp_nonce_field( 'seovault_schema_template', 'seovault_schema_nonce' );

		$post_id      = (int) get_the_ID();
		$data         = self::get_document( $post_id );
		$schema_types = self::get_template_builder_types();
		$current_type = is_array( $data ) ? (string) ( $data['type'] ?? '' ) : '';
		$conditions   = ( is_array( $data ) && isset( $data['metadata']['displayConditions'] ) && is_array( $data['metadata']['displayConditions'] ) )
			? $data['metadata']['displayConditions']
			: array();
		if ( empty( $conditions ) ) {
			$conditions = array(
				array(
					'condition'    => 'include',
					'category'     => 'singular',
					'type'         => '',
					'value'        => 0,
					'postTaxonomy' => '',
				),
			);
		}
		$targeting_errors = get_post_meta( $post_id, '_seovault_schema_template_targeting_errors', true );
		$targeting_errors = is_array( $targeting_errors ) ? $targeting_errors : array();
		$post_types       = self::get_public_post_type_choices();
		$taxonomies       = self::get_public_taxonomy_choices();
		$payload          = is_array( $data ) ? $data : array();
		?>
		<div id="seovault-schema-template-editor" class="seovaultseo-schema-template-editor">
			<input type="hidden" name="seovault_schema_data" id="seovault-schema-data" value="<?php echo esc_attr( wp_json_encode( $payload ) ); ?>" />

			<?php if ( ! empty( $targeting_errors ) ) : ?>
				<div class="notice notice-warning inline" role="status">
					<p>
						<?php esc_html_e( 'This template was kept as a draft because targeting is incomplete or too broad for a specialized type.', 'seovault-ai' ); ?>
					</p>
				</div>
			<?php endif; ?>

			<table class="form-table" role="presentation">
				<tr>
					<th scope="row">
						<label for="seovault-schema-type-select"><?php esc_html_e( 'Schema Type', 'seovault-ai' ); ?></label>
					</th>
					<td>
						<select
							id="seovault-schema-type-select"
							name="seovault_schema_type"
							class="regular-text seovaultseo-select"
						>
							<option value=""><?php esc_html_e( '— Select Type —', 'seovault-ai' ); ?></option>
							<?php foreach ( $schema_types as $type => $label ) : ?>
								<option value="<?php echo esc_attr( $type ); ?>" <?php selected( $current_type, $type ); ?>>
									<?php echo esc_html( $label ); ?>
								</option>
							<?php endforeach; ?>
						</select>
						<p class="description">
							<?php esc_html_e( 'Specialized types only. Automatic foundational schema is not configured here.', 'seovault-ai' ); ?>
						</p>
					</td>
				</tr>
			</table>

			<div id="seovault-schema-fields-container" class="seovaultseo-schema-template-fields">
				<?php if ( ! empty( $payload['fields'] ) && is_array( $payload['fields'] ) ) : ?>
					<p class="description"><?php esc_html_e( 'Stored field values will be applied when this template matches. Incomplete specialized values stay withheld until readiness passes.', 'seovault-ai' ); ?></p>
				<?php else : ?>
					<p class="description"><?php esc_html_e( 'Choose a specialized type and configure display conditions. Field values can be supplied via import or stored template data.', 'seovault-ai' ); ?></p>
				<?php endif; ?>
			</div>

			<section
				class="seovaultseo-schema-template-conditions"
				aria-labelledby="seovault-schema-conditions-heading"
			>
				<h3 id="seovault-schema-conditions-heading"><?php esc_html_e( 'Display Conditions', 'seovault-ai' ); ?></h3>
				<p class="description" id="seovault-schema-conditions-help">
					<?php esc_html_e( 'Include or exclude post types, taxonomies, archives, specific content, and supported contexts. Specialized templates cannot target the entire site or all posts/pages.', 'seovault-ai' ); ?>
				</p>

				<div
					id="seovault-schema-conditions-container"
					class="seovaultseo-schema-conditions"
					data-conditions="<?php echo esc_attr( wp_json_encode( $conditions ) ); ?>"
					aria-describedby="seovault-schema-conditions-help"
				>
					<table class="widefat striped seovaultseo-schema-conditions-table" role="table">
						<thead>
							<tr>
								<th scope="col"><?php esc_html_e( 'Rule', 'seovault-ai' ); ?></th>
								<th scope="col"><?php esc_html_e( 'Category', 'seovault-ai' ); ?></th>
								<th scope="col"><?php esc_html_e( 'Target', 'seovault-ai' ); ?></th>
								<th scope="col"><?php esc_html_e( 'ID / term', 'seovault-ai' ); ?></th>
								<th scope="col"><span class="screen-reader-text"><?php esc_html_e( 'Actions', 'seovault-ai' ); ?></span></th>
							</tr>
						</thead>
						<tbody id="seovault-schema-conditions-rows">
							<?php foreach ( $conditions as $index => $row ) : ?>
								<?php self::render_condition_row( (int) $index, $row, $post_types, $taxonomies ); ?>
							<?php endforeach; ?>
						</tbody>
					</table>
					<p>
						<button type="button" class="button" id="seovault-schema-condition-add">
							<?php esc_html_e( 'Add condition', 'seovault-ai' ); ?>
						</button>
					</p>
				</div>

				<div
					id="seovault-schema-scope-estimate"
					class="seovaultseo-schema-scope"
					role="status"
					aria-live="polite"
				>
					<?php
					$scope = DisplayConditionsSanitizer::estimate_scope(
						DisplayConditionsSanitizer::sanitize( $conditions )['conditions']
					);
					echo esc_html( $scope['label'] );
					?>
				</div>
				<p id="seovault-schema-targeting-alert" class="seovaultseo-schema-targeting-alert" role="alert" hidden></p>
			</section>

			<section
				class="seovaultseo-schema-template-preview"
				aria-labelledby="seovault-schema-preview-heading"
			>
				<h3 id="seovault-schema-preview-heading"><?php esc_html_e( 'Preview before publish', 'seovault-ai' ); ?></h3>
				<p class="description">
					<?php esc_html_e( 'Test this template against a selected post to confirm matching and readiness before publishing.', 'seovault-ai' ); ?>
				</p>
				<p>
					<label for="seovault-schema-preview-post-id">
						<?php esc_html_e( 'Preview post ID', 'seovault-ai' ); ?>
					</label>
					<input
						type="number"
						min="1"
						class="small-text"
						id="seovault-schema-preview-post-id"
						name="seovault_schema_preview_post_id"
						value=""
					/>
					<button type="button" class="button button-secondary" id="seovault-schema-preview-btn">
						<?php esc_html_e( 'Preview', 'seovault-ai' ); ?>
					</button>
				</p>
				<div
					id="seovault-schema-preview-result"
					class="seovaultseo-schema-preview-result"
					role="status"
					aria-live="polite"
				></div>
			</section>

			<template id="seovault-schema-condition-row-template">
				<?php
				self::render_condition_row(
					'__INDEX__',
					array(
						'condition'    => 'include',
						'category'     => 'singular',
						'type'         => '',
						'value'        => 0,
						'postTaxonomy' => '',
					),
					$post_types,
					$taxonomies
				);
				?>
			</template>
		</div>
		<?php
	}

	/**
	 * Render one condition row.
	 *
	 * @param int|string                           $index      Row index.
	 * @param array                                $row        Condition.
	 * @param array<int,array{value:string,label:string}> $post_types Post types.
	 * @param array<int,array{value:string,label:string}> $taxonomies Taxonomies.
	 * @return void
	 */
	private static function render_condition_row( $index, array $row, array $post_types, array $taxonomies ) {
		$operator = (string) ( $row['condition'] ?? 'include' );
		$category = (string) ( $row['category'] ?? 'singular' );
		$type     = (string) ( $row['type'] ?? '' );
		$value    = (int) ( $row['value'] ?? 0 );
		$taxonomy = (string) ( $row['postTaxonomy'] ?? '' );
		$name     = 'seovault_display_conditions[' . $index . ']';
		?>
		<tr class="seovaultseo-schema-condition-row">
			<td>
				<label class="screen-reader-text" for="seovault-dc-op-<?php echo esc_attr( (string) $index ); ?>">
					<?php esc_html_e( 'Include or exclude', 'seovault-ai' ); ?>
				</label>
				<select id="seovault-dc-op-<?php echo esc_attr( (string) $index ); ?>" name="<?php echo esc_attr( $name ); ?>[condition]">
					<option value="include" <?php selected( $operator, 'include' ); ?>><?php esc_html_e( 'Include', 'seovault-ai' ); ?></option>
					<option value="exclude" <?php selected( $operator, 'exclude' ); ?>><?php esc_html_e( 'Exclude', 'seovault-ai' ); ?></option>
				</select>
			</td>
			<td>
				<label class="screen-reader-text" for="seovault-dc-cat-<?php echo esc_attr( (string) $index ); ?>">
					<?php esc_html_e( 'Category', 'seovault-ai' ); ?>
				</label>
				<select id="seovault-dc-cat-<?php echo esc_attr( (string) $index ); ?>" name="<?php echo esc_attr( $name ); ?>[category]" class="seovaultseo-dc-category">
					<option value="singular" <?php selected( $category, 'singular' ); ?>><?php esc_html_e( 'Singular', 'seovault-ai' ); ?></option>
					<option value="archive" <?php selected( $category, 'archive' ); ?>><?php esc_html_e( 'Archive', 'seovault-ai' ); ?></option>
					<option value="general" <?php selected( $category, 'general' ); ?>><?php esc_html_e( 'Context', 'seovault-ai' ); ?></option>
				</select>
			</td>
			<td>
				<label class="screen-reader-text" for="seovault-dc-type-<?php echo esc_attr( (string) $index ); ?>">
					<?php esc_html_e( 'Target', 'seovault-ai' ); ?>
				</label>
				<select id="seovault-dc-type-<?php echo esc_attr( (string) $index ); ?>" name="<?php echo esc_attr( $name ); ?>[type]" class="seovaultseo-dc-type">
					<option value=""><?php esc_html_e( '— Choose —', 'seovault-ai' ); ?></option>
					<optgroup label="<?php echo esc_attr__( 'Post types', 'seovault-ai' ); ?>">
						<?php foreach ( $post_types as $pt ) : ?>
							<option value="<?php echo esc_attr( $pt['value'] ); ?>" <?php selected( $type, $pt['value'] ); ?>>
								<?php echo esc_html( $pt['label'] ); ?>
							</option>
						<?php endforeach; ?>
					</optgroup>
					<optgroup label="<?php echo esc_attr__( 'Contexts', 'seovault-ai' ); ?>">
						<option value="front" <?php selected( $type, 'front' ); ?>><?php esc_html_e( 'Front page', 'seovault-ai' ); ?></option>
						<option value="blog" <?php selected( $type, 'blog' ); ?>><?php esc_html_e( 'Posts page / blog index', 'seovault-ai' ); ?></option>
						<option value="search" <?php selected( $type, 'search' ); ?>><?php esc_html_e( 'Search results', 'seovault-ai' ); ?></option>
						<option value="404" <?php selected( $type, '404' ); ?>><?php esc_html_e( '404 page', 'seovault-ai' ); ?></option>
					</optgroup>
					<optgroup label="<?php echo esc_attr__( 'Archives', 'seovault-ai' ); ?>">
						<option value="author" <?php selected( $type, 'author' ); ?>><?php esc_html_e( 'Author archive', 'seovault-ai' ); ?></option>
						<option value="category" <?php selected( $type, 'category' ); ?>><?php esc_html_e( 'Category archive', 'seovault-ai' ); ?></option>
						<option value="post_tag" <?php selected( $type, 'post_tag' ); ?>><?php esc_html_e( 'Tag archive', 'seovault-ai' ); ?></option>
						<?php foreach ( $taxonomies as $tax ) : ?>
							<option value="<?php echo esc_attr( $tax['value'] ); ?>" <?php selected( $type, $tax['value'] ); ?>>
								<?php echo esc_html( sprintf( /* translators: %s: taxonomy label */ __( '%s archive', 'seovault-ai' ), $tax['label'] ) ); ?>
							</option>
						<?php endforeach; ?>
					</optgroup>
				</select>
				<label class="screen-reader-text" for="seovault-dc-tax-<?php echo esc_attr( (string) $index ); ?>">
					<?php esc_html_e( 'Taxonomy for term match', 'seovault-ai' ); ?>
				</label>
				<select id="seovault-dc-tax-<?php echo esc_attr( (string) $index ); ?>" name="<?php echo esc_attr( $name ); ?>[postTaxonomy]" class="seovaultseo-dc-taxonomy">
					<option value=""><?php esc_html_e( 'No term filter', 'seovault-ai' ); ?></option>
					<?php foreach ( $taxonomies as $tax ) : ?>
						<option value="<?php echo esc_attr( $tax['value'] ); ?>" <?php selected( $taxonomy, $tax['value'] ); ?>>
							<?php echo esc_html( $tax['label'] ); ?>
						</option>
					<?php endforeach; ?>
				</select>
			</td>
			<td>
				<label class="screen-reader-text" for="seovault-dc-value-<?php echo esc_attr( (string) $index ); ?>">
					<?php esc_html_e( 'Specific post or term ID (optional)', 'seovault-ai' ); ?>
				</label>
				<input
					type="number"
					min="0"
					class="small-text seovaultseo-dc-value"
					id="seovault-dc-value-<?php echo esc_attr( (string) $index ); ?>"
					name="<?php echo esc_attr( $name ); ?>[value]"
					value="<?php echo esc_attr( (string) $value ); ?>"
				/>
			</td>
			<td>
				<button type="button" class="button-link-delete seovaultseo-dc-remove">
					<?php esc_html_e( 'Remove', 'seovault-ai' ); ?>
				</button>
			</td>
		</tr>
		<?php
	}
}
