<?php
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Schema.org Management System
 * 
 * Handles insertion, storage, and management of Schema.org markup
 * 
 * @package Aysa
 * @since 1.0.0
 */

class AysaSchemaManager {
    private static function get_project_id_for_wallet() {
        $project_id = intval(get_option('aysa_project_id', 0));
        if ($project_id > 0) {
            return $project_id;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
        if (isset($_REQUEST['project_id'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed callback inventory: this read-only/core lifecycle request is authenticated by WordPress or an enclosing guard and performs no unapproved write.
            $project_id = intval(wp_unslash($_REQUEST['project_id']));
            if ($project_id > 0) {
                return $project_id;
            }
        }

        return 0;
    }

    private static function track_wallet_usage($action_key, $reference, $fallback_cost_cents, $meta = array(), $endpoint = '') {
        $project_id = self::get_project_id_for_wallet();
        if ($project_id <= 0 || !defined('AYSA_PLUGIN_DIR')) {
            return;
        }

        if (!class_exists('Aysa_Wallet_Manager')) {
            $wallet_file = AYSA_PLUGIN_DIR . 'includes/class-aysa-wallet-manager.php';
            if (file_exists($wallet_file)) {
                require_once $wallet_file;
            }
        }
        if (!class_exists('Aysa_Wallet_Manager')) {
            return;
        }

        $wallet = Aysa_Wallet_Manager::get_instance();
        if (!method_exists($wallet, 'track_action_usage')) {
            return;
        }

        $wallet->track_action_usage(
            $project_id,
            sanitize_key((string) $action_key),
            (string) $reference,
            is_array($meta) ? $meta : array(),
            intval($fallback_cost_cents),
            (string) $endpoint
        );
    }

    /**
     * Normalize schema URLs to avoid mismatch between insert/read checks.
     */
    private static function normalize_schema_url($url) {
        $url = trim((string)$url);
        if ($url === '') {
            return '';
        }

        $url = esc_url_raw($url);
        if ($url === '') {
            return '';
        }

        $parts = wp_parse_url($url);
        if (!$parts || empty($parts['host'])) {
            return untrailingslashit($url) . '/';
        }

        $scheme = isset($parts['scheme']) ? strtolower($parts['scheme']) : (is_ssl() ? 'https' : 'http');
        $host = strtolower($parts['host']);
        $port = isset($parts['port']) ? ':' . intval($parts['port']) : '';
        $path = isset($parts['path']) ? $parts['path'] : '/';
        if ($path === '') {
            $path = '/';
        }
        $path = '/' . ltrim($path, '/');
        if (substr($path, -1) !== '/') {
            $path .= '/';
        }

        return $scheme . '://' . $host . $port . $path;
    }
    
    /**
     * Initialize the Schema Manager
     */
    public static function init() {
        add_action('wp_head', array(__CLASS__, 'output_schema_markup'));
        add_filter('wpseo_json_ld_output', array(__CLASS__, 'filter_wpseo_json_ld_output'), 20, 2);
        add_filter('rank_math/json_ld', array(__CLASS__, 'filter_rank_math_json_ld'), 99, 2);
        add_filter('woocommerce_structured_data_product', array(__CLASS__, 'filter_woocommerce_structured_data_product'), 99, 2);
        add_action('wp_ajax_aysa_insert_schema', array(__CLASS__, 'insert_schema_ajax'));
        add_action('wp_ajax_aysa_remove_schema', array(__CLASS__, 'remove_schema_ajax'));
        add_action('wp_ajax_aysa_get_page_schemas', array(__CLASS__, 'get_page_schemas_ajax'));
        add_action('wp_ajax_aysa_get_page_meta', array(__CLASS__, 'get_page_meta_ajax'));
        add_action('wp_ajax_aysa_check_faq_content', array(__CLASS__, 'check_faq_content_ajax'));
        
        // Add AJAX action to disable ALL schema markup
        add_action('wp_ajax_aysa_disable_all_schemas', array(__CLASS__, 'disable_all_schemas_ajax'));
        
        // Add AJAX action to physically clean LD+JSON from pages
        add_action('wp_ajax_aysa_clean_page_schemas', array(__CLASS__, 'clean_page_schemas_ajax'));
        
        // Create database table for schema storage
        add_action('init', array(__CLASS__, 'maybe_create_schema_table'));
    }

    /**
     * Guard admin-only schema AJAX actions.
     */
    private static function verify_schema_ajax_access() {
        if (!is_user_logged_in()) {
            wp_send_json_error('Authentication required');
            return false;
        }
        if (!current_user_can('manage_options') && !current_user_can('edit_posts')) {
            wp_send_json_error('Permission denied');
            return false;
        }
        return true;
    }
    
    /**
     * Maybe create database table for storing schemas
     */
    public static function maybe_create_schema_table() {
        global $wpdb;
        
        $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
        
        // Check if table exists
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
            self::create_schema_table();
        }
    }
    
    /**
     * Create database table for storing schemas
     */
    public static function create_schema_table() {
        global $wpdb;
        
        $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
        
        $charset_collate = $wpdb->get_charset_collate();
        
        $sql = "CREATE TABLE $table_name (
            id mediumint(9) NOT NULL AUTO_INCREMENT,
            url varchar(500) NOT NULL,
            page_id int(11) DEFAULT NULL,
            schema_type varchar(50) NOT NULL,
            schema_data longtext NOT NULL,
            is_active tinyint(1) DEFAULT 1,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY url_type (url, schema_type),
            KEY page_id (page_id),
            KEY is_active (is_active)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
    }
    
    /**
     * Output schema markup in wp_head
     */
    public static function output_schema_markup() {
        // Check if all schema output is disabled
        if (get_option('aysa_disable_all_schema_output', false)) {
            aysa_debug_log("AYSA Schema: All schema output is disabled via option");
            return;
        }
        
        global $wpdb;
        
        $current_url = self::normalize_schema_url(self::get_current_url());
        $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
        
        // Debug logging
        aysa_debug_log('AYSA Schema Output - Current URL: ' . $current_url);
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $schemas = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM $table_name WHERE url = %s AND is_active = 1 ORDER BY created_at ASC",
            $current_url
        ));
        
        aysa_debug_log('AYSA Schema Output - Found schemas: ' . count($schemas));
        
        if (empty($schemas)) {
            return;
        }
        
        $graph_payloads = array();
        foreach ($schemas as $schema) {
            $schema_data = json_decode($schema->schema_data, true);
            if ($schema_data) {
                $schema_data = self::enrich_schema_payload_for_output($schema->schema_type, $schema_data, $current_url, intval($schema->page_id ?? 0));
            }
            if ($schema_data && self::is_schema_payload_output_ready($schema->schema_type, $schema_data)) {
                $graph_payloads[] = array(
                    'schema_type' => sanitize_key((string) $schema->schema_type),
                    'page_id' => intval($schema->page_id ?? 0),
                    'data' => $schema_data,
                );
            }
        }

        $schema_graph = self::build_unified_schema_graph($graph_payloads, $current_url);
        if (empty($schema_graph['@graph'])) {
            return;
        }

        $comment_bits = array(
            'AYSA unified schema graph',
            'nodes=' . count($schema_graph['@graph']),
            'url=' . esc_url_raw($current_url),
        );
        if (defined('AYSA_VERSION')) {
            $comment_bits[] = 'version=' . sanitize_text_field((string) AYSA_VERSION);
        }

        echo "\n<!-- AYSA Schema.org JSON-LD -->\n";
        echo '<!-- ' . esc_html(implode(' | ', $comment_bits)) . ' -->' . "\n";
        echo '<script type="application/ld+json">' . "\n";
        echo wp_json_encode($schema_graph, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . "\n";
        echo '</script>' . "\n";
        echo "<!-- /AYSA Schema.org JSON-LD -->\n\n";
    }

    /**
     * Build one canonical JSON-LD graph for the current URL.
     *
     * @param array  $payloads    Enriched schema payloads for this URL.
     * @param string $current_url Normalized current URL.
     * @return array
     */
    private static function build_unified_schema_graph(array $payloads, $current_url) {
        $nodes = array();
        foreach ($payloads as $payload) {
            if (!is_array($payload) || !is_array($payload['data'] ?? null)) {
                continue;
            }
            foreach (self::extract_schema_graph_nodes($payload['data']) as $node) {
                $nodes[] = $node;
            }
        }

        $nodes = self::dedupe_schema_graph_nodes($nodes);
        $nodes = self::select_canonical_schema_nodes($nodes, $current_url);

        return array(
            '@context' => 'https://schema.org',
            '@graph' => array_values($nodes),
        );
    }

    /**
     * Extract graph nodes from a schema payload.
     *
     * @param array $schema_data Decoded schema payload.
     * @return array
     */
    private static function extract_schema_graph_nodes(array $schema_data) {
        $raw_nodes = (isset($schema_data['@graph']) && is_array($schema_data['@graph']))
            ? $schema_data['@graph']
            : array($schema_data);

        $nodes = array();
        foreach ($raw_nodes as $node) {
            if (!is_array($node)) {
                continue;
            }
            unset($node['@context']);
            if (empty($node['@type'])) {
                continue;
            }
            $nodes[] = $node;
        }
        return $nodes;
    }

    /**
     * Deduplicate graph nodes by @id first, then by type and canonical URL/name.
     *
     * @param array $nodes Schema graph nodes.
     * @return array
     */
    private static function dedupe_schema_graph_nodes(array $nodes) {
        $deduped = array();
        foreach ($nodes as $node) {
            if (!is_array($node)) {
                continue;
            }
            $key = self::schema_graph_node_key($node);
            if (!isset($deduped[$key]) || count($node) > count($deduped[$key])) {
                $deduped[$key] = $node;
            }
        }
        return array_values($deduped);
    }

    /**
     * Keep one primary page entity and safe supporting nodes in the canonical graph.
     *
     * @param array  $nodes       Deduplicated schema nodes.
     * @param string $current_url Current URL.
     * @return array
     */
    private static function select_canonical_schema_nodes(array $nodes, $current_url) {
        $preferred_primary_types = self::preferred_primary_schema_types();
        $supporting_types = array(
            'Organization',
            'LocalBusiness',
            'Store',
            'Florist',
            'WebSite',
            'BreadcrumbList',
            'FAQPage',
            'ItemList',
        );

        $primary_index = null;
        foreach ($preferred_primary_types as $preferred_type) {
            foreach ($nodes as $index => $node) {
                if (in_array($preferred_type, self::node_types($node), true)) {
                    $primary_index = $index;
                    break 2;
                }
            }
        }

        if ($primary_index === null) {
            foreach ($nodes as $index => $node) {
                if (array_intersect(self::node_types($node), $supporting_types)) {
                    continue;
                }
                $primary_index = $index;
                break;
            }
        }

        $selected = array();
        $seen_support_types = array();
        foreach ($nodes as $index => $node) {
            $types = self::node_types($node);
            $is_support = (bool) array_intersect($types, $supporting_types);
            if ($primary_index !== null && $index === $primary_index) {
                $selected[] = self::normalize_primary_schema_node($node, $current_url);
                continue;
            }
            if (!$is_support) {
                continue;
            }

            $support_key = implode('|', array_intersect($types, $supporting_types));
            if ($support_key !== '' && isset($seen_support_types[$support_key])) {
                continue;
            }
            if ($support_key !== '') {
                $seen_support_types[$support_key] = true;
            }
            $selected[] = $node;
        }

        return $selected;
    }

    /**
     * Determine preferred primary schema types from the current WordPress context.
     *
     * @return array
     */
    private static function preferred_primary_schema_types() {
        if (function_exists('is_singular') && is_singular('product')) {
            return array('Product', 'WebPage');
        }
        if ((function_exists('is_product_category') && is_product_category()) || is_tax() || is_category() || is_tag() || is_archive()) {
            return array('CollectionPage', 'ItemList', 'WebPage');
        }
        if (is_singular('post')) {
            return array('BlogPosting', 'Article', 'WebPage');
        }
        if (is_front_page() || is_home()) {
            return array('WebPage', 'WebSite');
        }
        return array('WebPage', 'Article', 'BlogPosting', 'Product', 'CollectionPage');
    }

    /**
     * Normalize primary node identifiers to the current URL where safe.
     *
     * @param array  $node        Schema node.
     * @param string $current_url Current URL.
     * @return array
     */
    private static function normalize_primary_schema_node(array $node, $current_url) {
        $types = self::node_types($node);
        if (in_array('CollectionPage', $types, true) || in_array('WebPage', $types, true)) {
            $node['url'] = $current_url;
            if (empty($node['@id'])) {
                $node['@id'] = untrailingslashit((string) $current_url) . '/#webpage';
            }
        }
        return $node;
    }

    /**
     * Build a stable dedupe key for a graph node.
     *
     * @param array $node Schema node.
     * @return string
     */
    private static function schema_graph_node_key(array $node) {
        $id = trim((string) ($node['@id'] ?? ''));
        if ($id !== '') {
            return 'id:' . strtolower($id);
        }

        $types = self::node_types($node);
        $type = $types !== array('') ? implode('|', $types) : 'Thing';
        $url = trim((string) ($node['url'] ?? ''));
        if ($url === '' && isset($node['mainEntityOfPage']['@id'])) {
            $url = trim((string) $node['mainEntityOfPage']['@id']);
        }
        $name = trim((string) ($node['name'] ?? $node['headline'] ?? ''));
        return 'node:' . strtolower($type . '|' . $url . '|' . $name);
    }

    /**
     * Disable Yoast schema output when AYSA owns the current URL.
     *
     * @param mixed  $data    Yoast schema output.
     * @param string $context Optional Yoast context argument.
     * @return mixed
     */
    public static function filter_wpseo_json_ld_output($data, $context = '') {
        if (is_admin()) {
            return $data;
        }

        $current_url = self::normalize_schema_url(self::get_current_url());
        if ($current_url === '') {
            return $data;
        }

        if (self::has_active_schema_for_url($current_url)) {
            return false;
        }

        return $data;
    }

    /**
     * Disable Rank Math schema output when AYSA owns the current URL.
     *
     * @param array $data Rank Math schema data.
     * @param mixed $jsonld Rank Math JSON-LD context.
     * @return array
     */
    public static function filter_rank_math_json_ld($data, $jsonld = null) {
        if (is_admin()) {
            return $data;
        }

        $current_url = self::normalize_schema_url(self::get_current_url());
        if ($current_url === '') {
            return $data;
        }

        if (self::has_active_schema_for_url($current_url)) {
            return array();
        }

        return $data;
    }

    /**
     * Disable WooCommerce Product schema when AYSA owns the current URL.
     *
     * @param array      $markup  Structured data markup.
     * @param WC_Product $product Product object.
     * @return array
     */
    public static function filter_woocommerce_structured_data_product($markup, $product = null) {
        if (is_admin()) {
            return $markup;
        }

        $current_url = self::normalize_schema_url(self::get_current_url());
        if ($current_url === '') {
            return $markup;
        }

        if (self::has_active_schema_for_url($current_url, 'product')) {
            return array();
        }

        return $markup;
    }
    
    /**
     * Insert schema via AJAX
     */
    public static function insert_schema_ajax() {
        if (!self::verify_schema_ajax_access()) {
            return;
        }
        // Check nonce for security
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        if (!wp_verify_nonce($nonce, 'aysa_schema_nonce')) {
            wp_die('Security check failed');
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- normalize_schema_url applies esc_url_raw and rejects values without a valid host.
        $url = self::normalize_schema_url(isset($_POST['url']) ? (string) wp_unslash($_POST['url']) : '');
        $schema_type = sanitize_text_field(isset($_POST['schemaType']) ? (string) wp_unslash($_POST['schemaType']) : '');
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON is shape-validated before storage and encoded with JSON_HEX_* flags at the script output boundary.
        $schema_data = isset($_POST['schemaData']) ? (string) wp_unslash($_POST['schemaData']) : '';
        $replace_existing = isset($_POST['replaceExisting']) ? rest_sanitize_boolean(wp_unslash($_POST['replaceExisting'])) : true;
        
        // Debug logging
        aysa_debug_log('AYSA Schema Insert - URL: ' . $url);
        aysa_debug_log('AYSA Schema Insert - Type: ' . $schema_type);
        aysa_debug_log('AYSA Schema Insert - Data length: ' . strlen($schema_data));
        aysa_debug_log('AYSA Schema Insert - Data preview: ' . substr($schema_data, 0, 500));
        
        if (empty($url) || empty($schema_type) || empty($schema_data)) {
            wp_send_json_error('URL, schema type, and schema data are required');
            return;
        }
        
        // Validate JSON
        $decoded_schema = json_decode($schema_data, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            wp_send_json_error('Invalid JSON schema data: ' . json_last_error_msg() . ' | Data received: ' . substr($schema_data, 0, 200));
            return;
        }
        
        try {
            $result = self::insert_schema($url, $schema_type, $schema_data, $replace_existing);
            
            if ($result) {
                self::track_wallet_usage(
                    'aysa_insert_schema',
                    'insert_schema',
                    12,
                    array(
                        'module' => 'technical_schema',
                        'url' => $url,
                        'schema_type' => $schema_type,
                        'replace_existing' => $replace_existing ? 1 : 0,
                    ),
                    '/v1/technical/schema/insert'
                );
                wp_send_json_success(array(
                    'message' => 'Schema inserted successfully',
                    'url' => $url,
                    'type' => $schema_type,
                    'id' => $result
                ));
            } else {
                wp_send_json_error('Failed to insert schema');
            }
        } catch (Exception $e) {
            wp_send_json_error($e->getMessage());
        }
    }
    
    /**
     * Remove schema via AJAX
     */
    public static function remove_schema_ajax() {
        if (!self::verify_schema_ajax_access()) {
            return;
        }
        // Check nonce for security
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        if (!wp_verify_nonce($nonce, 'aysa_schema_nonce')) {
            wp_send_json_error('Security check failed');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- normalize_schema_url applies esc_url_raw and rejects values without a valid host.
        $url = self::normalize_schema_url(isset($_POST['url']) ? (string) wp_unslash($_POST['url']) : '');
        $schema_type = sanitize_text_field(isset($_POST['schemaType']) ? (string) wp_unslash($_POST['schemaType']) : '');
        
        if (empty($url)) {
            wp_send_json_error('URL is required');
            return;
        }
        
        // If no schema type provided, try to remove any schema for this URL
        if (empty($schema_type)) {
            $schema_type = null;
        }
        
        try {
            // Debug: Log what we're trying to remove
            aysa_debug_log("AYSA Schema Remove: URL = $url, Type = $schema_type");
            
            // First check if schema exists
            global $wpdb;
            $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
            
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $existing_schemas = $wpdb->get_results($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT * FROM $table_name WHERE url = %s",
                $url
            ));
            
            aysa_debug_log("AYSA Schema Remove: Found " . count($existing_schemas) . " schemas for URL");
            foreach ($existing_schemas as $schema) {
                aysa_debug_log("AYSA Schema Remove: Schema ID {$schema->id}, Type: {$schema->schema_type}, Active: {$schema->is_active}");
            }
            
            // For automatic schema (generated by AYSA_Structured_Data class),
            // we need to add URL to blacklist instead of removing from database
            
            // First try to remove from database (for manually inserted schemas)
            $result = self::remove_schema($url, $schema_type);
            
            // If specific type failed, try removing any schema for this URL
            if (!$result && !empty($schema_type)) {
                aysa_debug_log("AYSA Schema Remove: Specific type failed, trying to remove any schema for URL");
                $result = self::remove_schema($url, null);
            }
            
            // Always add URL to schema blacklist to prevent automatic generation
            $blacklist = get_option('aysa_schema_blacklist', array());
            if (!in_array($url, $blacklist)) {
                $blacklist[] = $url;
                update_option('aysa_schema_blacklist', $blacklist);
                aysa_debug_log("AYSA Schema Remove: Added URL to blacklist: $url");
                $result = true; // Consider successful if added to blacklist
            }
            
            aysa_debug_log("AYSA Schema Remove: Delete result = " . ($result ? 'SUCCESS' : 'FAILED'));
            
            if ($result) {
                self::track_wallet_usage(
                    'aysa_remove_schema',
                    'remove_schema',
                    12,
                    array(
                        'module' => 'technical_schema',
                        'url' => $url,
                        'schema_type' => $schema_type,
                    ),
                    '/v1/technical/schema/remove'
                );
                wp_send_json_success(array(
                    'message' => 'Schema removed successfully',
                    'url' => $url,
                    'type' => $schema_type,
                    'debug' => "Removed schema for $url (type: $schema_type)"
                ));
            } else {
                wp_send_json_error('Schema not found or already removed. Check: ' . count($existing_schemas) . ' schemas found for URL');
            }
        } catch (Exception $e) {
            aysa_debug_log("AYSA Schema Remove Error: " . $e->getMessage());
            wp_send_json_error($e->getMessage());
        }
    }
    
    /**
     * Get page schemas via AJAX
     */
    public static function get_page_schemas_ajax() {
        if (!self::verify_schema_ajax_access()) {
            return;
        }
        // Check nonce for security
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        if (!wp_verify_nonce($nonce, 'aysa_schema_nonce')) {
            wp_die('Security check failed');
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- normalize_schema_url applies esc_url_raw and rejects values without a valid host.
        $url = self::normalize_schema_url(isset($_POST['url']) ? (string) wp_unslash($_POST['url']) : '');
        
        if (empty($url)) {
            wp_send_json_error('URL is required');
            return;
        }
        
        try {
            $schemas = self::get_page_schemas($url);
            wp_send_json_success($schemas);
        } catch (Exception $e) {
            wp_send_json_error($e->getMessage());
        }
    }
    
    /**
     * Insert schema into database
     */
    public static function insert_schema($url, $schema_type, $schema_data, $replace_existing = true) {
        global $wpdb;
        
        $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
        $page_id = url_to_postid($url);
        $decoded_schema = json_decode($schema_data, true);
        if (is_array($decoded_schema)) {
            $decoded_schema = self::enrich_schema_payload_for_output($schema_type, $decoded_schema, $url, $page_id);
            $schema_data = wp_json_encode($decoded_schema, JSON_UNESCAPED_SLASHES);
        }

        // Keep one canonical schema type for products:
        // if product schema is being saved, remove stale generic webpage schema for same URL.
        if ($schema_type === 'product') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->delete(
                $table_name,
                array(
                    'url' => $url,
                    'schema_type' => 'webpage'
                ),
                array('%s', '%s')
            );
        }
        
        // Check if schema already exists
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id FROM $table_name WHERE url = %s AND schema_type = %s",
            $url, $schema_type
        ));
        
        if ($existing && $replace_existing) {
            // Update existing schema
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->update(
                $table_name,
                array(
                    'schema_data' => $schema_data,
                    'page_id' => $page_id,
                    'is_active' => 1,
                    'updated_at' => current_time('mysql')
                ),
                array('id' => $existing->id),
                array('%s', '%d', '%d', '%s'),
                array('%d')
            );
            
            return $result !== false ? $existing->id : false;
        } elseif (!$existing) {
            // Insert new schema
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->insert(
                $table_name,
                array(
                    'url' => $url,
                    'page_id' => $page_id,
                    'schema_type' => $schema_type,
                    'schema_data' => $schema_data,
                    'is_active' => 1,
                    'created_at' => current_time('mysql'),
                    'updated_at' => current_time('mysql')
                ),
                array('%s', '%d', '%s', '%s', '%d', '%s', '%s')
            );
            
            return $result !== false ? $wpdb->insert_id : false;
        }
        
        return false; // Schema exists and replace_existing is false
    }
    
    /**
     * Remove schema from database
     */
    public static function remove_schema($url, $schema_type = null) {
        global $wpdb;
        
        $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
        
        if ($schema_type) {
            // Remove specific schema type
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->delete(
                $table_name,
                array(
                    'url' => $url,
                    'schema_type' => $schema_type
                ),
                array('%s', '%s')
            );
        } else {
            // Remove all schemas for this URL
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->delete(
                $table_name,
                array('url' => $url),
                array('%s')
            );
        }
        
        return $result !== false && $result > 0;
    }
    
    /**
     * Get all schemas for a specific page
     */
    public static function get_page_schemas($url) {
        global $wpdb;
        
        $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $schemas = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM $table_name WHERE url = %s ORDER BY created_at ASC",
            $url
        ));
        
        $result = array();
        foreach ($schemas as $schema) {
            $result[] = array(
                'id' => $schema->id,
                'url' => $schema->url,
                'type' => $schema->schema_type,
                'data' => json_decode($schema->schema_data, true),
                'is_active' => (bool)$schema->is_active,
                'created_at' => $schema->created_at,
                'updated_at' => $schema->updated_at
            );
        }
        
        return $result;
    }

    /**
     * Check whether AYSA has an active schema for the current URL.
     *
     * @param string      $url         Normalized URL.
     * @param string|null $schema_type Optional schema type filter.
     * @return bool
     */
    private static function has_active_schema_for_url($url, $schema_type = null) {
        global $wpdb;

        $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
        $url = self::normalize_schema_url($url);
        if ($url === '') {
            return false;
        }

        if ($schema_type !== null && $schema_type !== '') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $count = (int) $wpdb->get_var($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
                "SELECT COUNT(1) FROM {$table_name} WHERE url = %s AND schema_type = %s AND is_active = 1",
                $url,
                $schema_type
            ));
            return $count > 0;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $count = (int) $wpdb->get_var($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT COUNT(1) FROM {$table_name} WHERE url = %s AND is_active = 1",
            $url
        ));

        return $count > 0;
    }

    /**
     * Prevent stale or weak payloads from being emitted on the frontend.
     *
     * @param string $schema_type Schema type stored in DB.
     * @param array  $schema_data Decoded schema payload.
     * @return bool
     */
    private static function is_schema_payload_output_ready($schema_type, array $schema_data) {
        $schema_type = sanitize_key((string) $schema_type);

        if ($schema_type !== 'product') {
            return true;
        }

        $nodes = (isset($schema_data['@graph']) && is_array($schema_data['@graph']))
            ? $schema_data['@graph']
            : array($schema_data);

        foreach ($nodes as $node) {
            if (!is_array($node)) {
                continue;
            }
            $node_type = $node['@type'] ?? '';
            $types = is_array($node_type) ? $node_type : array($node_type);
            if (!in_array('Product', $types, true)) {
                continue;
            }

            $offers = isset($node['offers']) ? $node['offers'] : array();
            if (isset($offers[0]) && is_array($offers[0])) {
                $offers = $offers[0];
            }

            $image = isset($node['image']) ? $node['image'] : array();
            if (is_string($image)) {
                $image = trim($image) !== '' ? array($image) : array();
            }

            return trim((string) ($node['name'] ?? '')) !== ''
                && trim((string) ($node['description'] ?? '')) !== ''
                && trim((string) ($node['sku'] ?? '')) !== ''
                && !empty($image)
                && trim((string) ($offers['price'] ?? $offers['lowPrice'] ?? '')) !== '';
        }

        return false;
    }

    private static function enrich_schema_payload_for_output($schema_type, array $schema_data, $url = '', $page_id = 0) {
        $schema_type = sanitize_key((string) $schema_type);
        if ($schema_type === 'article') {
            $author_url = self::resolve_article_author_url($page_id, $url);
            if ($author_url === '') {
                return $schema_data;
            }

            if (isset($schema_data['@graph']) && is_array($schema_data['@graph'])) {
                foreach ($schema_data['@graph'] as $index => $node) {
                    if (!is_array($node)) {
                        continue;
                    }
                    if (!self::node_is_article($node)) {
                        continue;
                    }
                    $schema_data['@graph'][$index] = self::inject_author_url_into_article_node($node, $author_url);
                }
                return $schema_data;
            }

            if (self::node_is_article($schema_data)) {
                return self::inject_author_url_into_article_node($schema_data, $author_url);
            }

            return $schema_data;
        }

        if (!in_array($schema_type, array('category', 'collectionpage'), true)) {
            return $schema_data;
        }

        $term = self::resolve_term_for_schema_url($url);
        if (!($term instanceof WP_Term)) {
            return $schema_data;
        }

        $item_list_elements = self::build_term_item_list_elements($term);
        if (isset($schema_data['@graph']) && is_array($schema_data['@graph'])) {
            $has_item_list = false;
            $has_breadcrumb = false;
            foreach ($schema_data['@graph'] as $index => $node) {
                if (!is_array($node)) {
                    continue;
                }
                $types = self::node_types($node);
                if (in_array('ItemList', $types, true)) {
                    $has_item_list = true;
                }
                if (in_array('BreadcrumbList', $types, true)) {
                    $has_breadcrumb = true;
                }
                if (!in_array('CollectionPage', $types, true)) {
                    continue;
                }
                $collection_url = self::normalize_schema_url((string) ($node['url'] ?? $url));
                $collection_title = trim((string) ($node['name'] ?? $term->name));
                $schema_data['@graph'][$index]['@id'] = untrailingslashit($collection_url) . '/#webpage';
                $schema_data['@graph'][$index]['url'] = $collection_url;
                $schema_data['@graph'][$index]['name'] = $collection_title !== '' ? $collection_title : sanitize_text_field((string) $term->name);
                $schema_data['@graph'][$index]['inLanguage'] = get_bloginfo('language') ?: 'ro-RO';
                $schema_data['@graph'][$index]['isPartOf'] = array(
                    '@id' => trailingslashit(home_url('/')) . '#website',
                );
                if (empty($node['description']) && !empty($term->description)) {
                    $schema_data['@graph'][$index]['description'] = wp_strip_all_tags((string) $term->description);
                }
                $schema_data['@graph'][$index]['breadcrumb'] = array(
                    '@id' => untrailingslashit($collection_url) . '/#breadcrumb',
                );
                if (!empty($item_list_elements)) {
                    $schema_data['@graph'][$index]['mainEntity'] = array(
                        '@id' => untrailingslashit($collection_url) . '/#itemlist',
                    );
                }
            }
            if (!$has_breadcrumb) {
                $breadcrumb = self::build_term_breadcrumb_node($term, $url);
                if (!empty($breadcrumb)) {
                    array_unshift($schema_data['@graph'], $breadcrumb);
                }
            } else {
                foreach ($schema_data['@graph'] as $index => $node) {
                    if (!is_array($node) || !in_array('BreadcrumbList', self::node_types($node), true)) {
                        continue;
                    }
                    $schema_data['@graph'][$index] = self::build_term_breadcrumb_node($term, $url, $node);
                }
            }
            if (!$has_item_list && !empty($item_list_elements)) {
                $schema_data['@graph'][] = array(
                    '@type' => 'ItemList',
                    '@id' => untrailingslashit((string) $url) . '/#itemlist',
                    'name' => sanitize_text_field((string) $term->name) . ' items',
                    'itemListOrder' => 'https://schema.org/ItemListOrderAscending',
                    'numberOfItems' => count($item_list_elements),
                    'itemListElement' => $item_list_elements,
                );
            }
            return $schema_data;
        }

        if (in_array('CollectionPage', self::node_types($schema_data), true) && !empty($item_list_elements)) {
            $schema_data['mainEntity'] = array(
                '@id' => untrailingslashit((string) $url) . '/#itemlist',
            );
        }

        return $schema_data;
    }

    private static function resolve_article_author_url($page_id = 0, $url = '') {
        $post_id = intval($page_id);
        if ($post_id <= 0 && !empty($url)) {
            $post_id = url_to_postid((string) $url);
        }
        if ($post_id <= 0) {
            return '';
        }

        $post = get_post($post_id);
        if (!($post instanceof WP_Post)) {
            return '';
        }

        $author_id = intval($post->post_author);
        if ($author_id <= 0) {
            return '';
        }

        $author_url = esc_url_raw(get_author_posts_url($author_id));
        if ($author_url !== '') {
            return $author_url;
        }

        $user_url = trim((string) get_the_author_meta('user_url', $author_id));
        return $user_url !== '' ? esc_url_raw($user_url) : '';
    }

    private static function node_is_article(array $node) {
        foreach (self::node_types($node) as $type) {
            if (in_array($type, array('Article', 'BlogPosting', 'NewsArticle'), true)) {
                return true;
            }
        }
        return false;
    }

    private static function node_types(array $node) {
        $node_type = $node['@type'] ?? '';
        return is_array($node_type) ? array_values($node_type) : array($node_type);
    }

    private static function inject_author_url_into_article_node(array $node, $author_url) {
        if (!isset($node['author']) || !is_array($node['author'])) {
            return $node;
        }

        if (isset($node['author'][0]) && is_array($node['author'][0])) {
            foreach ($node['author'] as $index => $author) {
                if (!is_array($author) || !empty($author['url'])) {
                    continue;
                }
                $node['author'][$index]['url'] = $author_url;
            }
            return $node;
        }

        if (empty($node['author']['url'])) {
            $node['author']['url'] = $author_url;
        }

        return $node;
    }

    private static function resolve_term_for_schema_url($url) {
        if (!is_admin()) {
            $queried = get_queried_object();
            if ($queried instanceof WP_Term) {
                return $queried;
            }
        }

        $normalized_url = self::normalize_schema_url((string) $url);
        if ($normalized_url === '') {
            return null;
        }

        $path = trim((string) wp_parse_url($normalized_url, PHP_URL_PATH), '/');
        if ($path === '') {
            return null;
        }
        $slug = basename($path);
        if ($slug === '') {
            return null;
        }

        foreach ((array) get_taxonomies(array('public' => true), 'names') as $taxonomy) {
            $taxonomy = sanitize_key((string) $taxonomy);
            if ($taxonomy === '' || !taxonomy_exists($taxonomy)) {
                continue;
            }
            $term = get_term_by('slug', $slug, $taxonomy);
            if (!($term instanceof WP_Term) || is_wp_error($term)) {
                continue;
            }
            $term_link = get_term_link($term);
            if (!is_wp_error($term_link) && self::normalize_schema_url((string) $term_link) === $normalized_url) {
                return $term;
            }
        }

        return null;
    }

    private static function build_term_item_list_elements($term) {
        if (!($term instanceof WP_Term) || !taxonomy_exists((string) $term->taxonomy) || !class_exists('WP_Query')) {
            return array();
        }

        $taxonomy = sanitize_key((string) $term->taxonomy);
        $query_post_types = array();
        if (
            (function_exists('taxonomy_is_product_attribute') && taxonomy_is_product_attribute($taxonomy))
            || in_array($taxonomy, array('product_cat', 'product_tag'), true)
        ) {
            $query_post_types = array('product');
        } else {
            $taxonomy_object = get_taxonomy($taxonomy);
            $object_types = is_object($taxonomy_object) ? (array) ($taxonomy_object->object_type ?? array()) : array();
            foreach ($object_types as $candidate_type) {
                $candidate_type = sanitize_key((string) $candidate_type);
                if ($candidate_type === '' || !post_type_exists($candidate_type)) {
                    continue;
                }
                $post_type_object = get_post_type_object($candidate_type);
                if (!$post_type_object || empty($post_type_object->public) || in_array($candidate_type, array('attachment', 'revision', 'nav_menu_item', 'product_variation'), true)) {
                    continue;
                }
                $query_post_types[] = $candidate_type;
            }
            if (empty($query_post_types)) {
                $query_post_types = array('post');
            }
        }

        $query = new WP_Query(array(
            'post_type' => array_values(array_unique($query_post_types)),
            'post_status' => 'publish',
            'posts_per_page' => 12,
            'ignore_sticky_posts' => true,
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Reviewed WordPress query: the taxonomy selector is required by the feature and is constrained by the surrounding post type, status, limit, or administrative scope.
            'tax_query' => array(
                array(
                    'taxonomy' => $taxonomy,
                    'field' => 'term_id',
                    'terms' => array(intval($term->term_id)),
                ),
            ),
            'fields' => 'ids',
        ));

        $elements = array();
        if ($query->have_posts()) {
            $position = 1;
            foreach ((array) $query->posts as $listed_post_id) {
                $listed_post_id = intval($listed_post_id);
                if ($listed_post_id <= 0) {
                    continue;
                }
                $listed_url = get_permalink($listed_post_id);
                if (!$listed_url) {
                    continue;
                }
                $entry = array(
                    '@type' => 'ListItem',
                    'position' => $position,
                    'url' => esc_url_raw((string) $listed_url),
                    'name' => sanitize_text_field((string) get_the_title($listed_post_id)),
                );
                $listed_image = get_the_post_thumbnail_url($listed_post_id, 'medium');
                if ($listed_image) {
                    $entry['image'] = esc_url_raw((string) $listed_image);
                }
                $elements[] = $entry;
                $position++;
            }
        }
        wp_reset_postdata();

        return $elements;
    }

    private static function build_term_breadcrumb_node($term, $url, array $existing = array()) {
        if (!($term instanceof WP_Term)) {
            return array();
        }

        $page_url = self::normalize_schema_url((string) $url);
        if ($page_url === '') {
            $term_link = get_term_link($term);
            if (is_wp_error($term_link)) {
                return array();
            }
            $page_url = self::normalize_schema_url((string) $term_link);
        }

        $items = array();
        $position = 1;
        $items[] = array(
            '@type' => 'ListItem',
            'position' => $position++,
            'name' => __('Prima pagină', 'aysaaiseo'),
            'item' => trailingslashit(home_url('/')),
        );

        $ancestors = array_reverse((array) get_ancestors(intval($term->term_id), (string) $term->taxonomy, 'taxonomy'));
        foreach ($ancestors as $ancestor_id) {
            $ancestor = get_term(intval($ancestor_id), (string) $term->taxonomy);
            if (!($ancestor instanceof WP_Term) || is_wp_error($ancestor)) {
                continue;
            }
            $ancestor_link = get_term_link($ancestor);
            if (is_wp_error($ancestor_link)) {
                continue;
            }
            $items[] = array(
                '@type' => 'ListItem',
                'position' => $position++,
                'name' => sanitize_text_field((string) $ancestor->name),
                'item' => self::normalize_schema_url((string) $ancestor_link),
            );
        }

        $items[] = array(
            '@type' => 'ListItem',
            'position' => $position,
            'name' => sanitize_text_field((string) $term->name),
            'item' => $page_url,
        );

        return array_merge($existing, array(
            '@type' => 'BreadcrumbList',
            '@id' => untrailingslashit($page_url) . '/#breadcrumb',
            'itemListElement' => $items,
        ));
    }
    
    /**
     * Get current page URL
     */
    private static function get_current_url() {
        global $wp;
        
        if (is_admin()) {
            return '';
        }
        
        // Get the current URL with proper handling of permalinks
        $current_url = home_url($wp->request);
        
        // Add trailing slash if needed (WordPress standard)
        if (!empty($wp->request) && substr($current_url, -1) !== '/') {
            $current_url .= '/';
        }
        
        return $current_url;
    }
    
    /**
     * AJAX handler for getting page meta data
     */
    public static function get_page_meta_ajax() {
        if (!self::verify_schema_ajax_access()) {
            return;
        }
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        if (!wp_verify_nonce($nonce, 'aysa_schema_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- normalize_schema_url applies esc_url_raw and rejects values without a valid host.
        $url = self::normalize_schema_url(isset($_POST['url']) ? (string) wp_unslash($_POST['url']) : '');
        if (empty($url)) {
            wp_send_json_error('URL is required');
            return;
        }
        
        // Get post ID from URL
        $post_id = url_to_postid($url);
        $title = '';
        $description = '';
        
        if ($post_id) {
            // Get post data
            $post = get_post($post_id);
            if ($post) {
                // Get actual page title - check for SEO title first
                $seo_title = get_post_meta($post_id, '_aysa_seo_title', true);
                if (!empty($seo_title)) {
                    $title = $seo_title;
                } else {
                    $yoast_title = get_post_meta($post_id, '_yoast_wpseo_title', true);
                    $title = !empty($yoast_title) ? $yoast_title : get_the_title($post_id);
                }
                
                // Try to get meta description from various sources
                $description = get_post_meta($post_id, '_aysa_seo_description', true);
                
                if (empty($description)) {
                    $description = get_post_meta($post_id, '_yoast_wpseo_metadesc', true);
                }
                
                if (empty($description)) {
                    $description = get_the_excerpt($post_id);
                }
                
                if (empty($description) && !empty($post->post_content)) {
                    $description = wp_trim_words(wp_strip_all_tags($post->post_content), 25);
                }
            }
        } else {
            // Handle category/tag URLs
            if (strpos($url, '/category/') !== false) {
                $slug = basename(wp_parse_url($url, PHP_URL_PATH));
                $term = get_term_by('slug', $slug, 'category');
                if ($term) {
                    $title = $term->name;
                    $description = !empty($term->description) ? $term->description : "Explore our {$term->name} category with quality content and resources.";
                }
            } elseif (strpos($url, '/tag/') !== false) {
                $slug = basename(wp_parse_url($url, PHP_URL_PATH));
                $term = get_term_by('slug', $slug, 'post_tag');
                if ($term) {
                    $title = $term->name;
                    $description = !empty($term->description) ? $term->description : "Browse all posts tagged with {$term->name}.";
                }
            } elseif ($url === home_url('/') || $url === home_url()) {
                // Homepage
                $title = get_bloginfo('name');
                $description = get_bloginfo('description') ?: "Welcome to " . get_bloginfo('name') . " - your trusted source for quality content.";
            }
        }
        
        // Fallback if still empty
        if (empty($title)) {
            $title = get_bloginfo('name');
        }
        
        if (empty($description)) {
            $description = "Learn more about {$title} on our website.";
        }
        
        // Debug logging
        aysa_debug_log("AYSA Page Meta: URL = $url");
        aysa_debug_log("AYSA Page Meta: Post ID = $post_id");
        aysa_debug_log("AYSA Page Meta: Title = $title");
        aysa_debug_log("AYSA Page Meta: Description = $description");
        
            // Detect real entity type
            $entity_type = self::detect_real_entity_type($url, $post_id);
            
            // Get additional product data if it's a WooCommerce product
            $product_data = array();
            if ($entity_type === 'product' && $post_id && class_exists('WooCommerce')) {
                $product_data = self::get_woocommerce_product_data($post_id);
            }
            
            // Debug logging
            aysa_debug_log("AYSA Entity Detection Final Result: URL = $url, Entity Type = $entity_type, Post ID = $post_id");
            if (!empty($product_data)) {
                aysa_debug_log("AYSA Product Data: " . json_encode($product_data));
            }
            
            $response_data = array(
                'title' => $title,
                'description' => $description,
                'entity_type' => $entity_type,
                'post_id' => $post_id,
                'debug_url' => $url // For debugging
            );
            
            // Merge product data if available
            if (!empty($product_data)) {
                $response_data = array_merge($response_data, $product_data);
            }
            
            wp_send_json_success($response_data);
    }
    
    /**
     * Detect real entity type based on WordPress data
     */
    private static function detect_real_entity_type($url, $post_id = null) {
        aysa_debug_log("AYSA Entity Detection START: URL = $url, Initial Post ID = $post_id");
        
        // If no post ID, try to get it from URL
        if (!$post_id) {
            $post_id = url_to_postid($url);
            aysa_debug_log("AYSA Entity Detection: url_to_postid() returned: $post_id");
        }
        
        // Homepage detection
        if ($url === home_url('/') || $url === home_url()) {
            aysa_debug_log("AYSA Entity Detection: Detected as homepage");
            return 'webpage'; // Homepage is a webpage
        }
        
        // Enhanced WordPress entity detection - check ALL posts/pages/products
        global $wpdb;
        
        // Method 1: Direct permalink matching - check all post types
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $all_posts = $wpdb->get_results("
            SELECT ID, post_type, post_title, post_name 
            FROM {$wpdb->posts} 
            WHERE post_status = 'publish' 
            AND post_type IN ('post', 'page', 'product')
            LIMIT 200
        ");
        
        foreach ($all_posts as $post_obj) {
            $post_url = get_permalink($post_obj->ID);
            if ($post_url === $url) {
                aysa_debug_log("AYSA Entity Detection: Found exact permalink match - ID: {$post_obj->ID}, Type: {$post_obj->post_type}, Title: {$post_obj->post_title}");
                
                // Return the correct entity type based on post_type
                switch ($post_obj->post_type) {
                    case 'product':
                        return 'product';
                    case 'post':
                        return 'article';
                    case 'page':
                        return 'webpage';
                    default:
                        return 'webpage';
                }
            }
        }
        
        aysa_debug_log("AYSA Entity Detection: No exact permalink match found, checking custom post types...");
        
        // Method 2: Check custom post types
        $custom_post_types = get_post_types(array('public' => true, '_builtin' => false), 'names');
        foreach ($custom_post_types as $post_type) {
            $custom_posts = get_posts(array(
                'post_type' => $post_type,
                'post_status' => 'publish',
                'numberposts' => 50
            ));
            
            foreach ($custom_posts as $custom_post) {
                $custom_url = get_permalink($custom_post->ID);
                if ($custom_url === $url) {
                    aysa_debug_log("AYSA Entity Detection: Found custom post type match - Type: $post_type, Title: {$custom_post->post_title}");
                    
                    // Determine entity type for custom post types
                    if (strpos($post_type, 'product') !== false) {
                        return 'product';
                    } else {
                        return 'webpage';
                    }
                }
            }
        }
        
        // Post-based detection
        if ($post_id) {
            $post = get_post($post_id);
            if ($post) {
                $post_type = $post->post_type;
                
                aysa_debug_log("AYSA Entity Detection: Post ID $post_id found with post_type: $post_type, title: " . $post->post_title);
                
                // Check for WooCommerce product
                if ($post_type === 'product') {
                    return 'product';
                }
                
                // Check for regular posts (blog articles)
                if ($post_type === 'post') {
                    return 'article';
                }
                
                // Check for pages
                if ($post_type === 'page') {
                    return 'webpage';
                }
                
                // Check for custom post types
                $post_type_object = get_post_type_object($post_type);
                if ($post_type_object && $post_type_object->public) {
                    // For custom post types, default to webpage unless it's product-like
                    if (strpos($post_type, 'product') !== false || strpos($post_type, 'shop') !== false) {
                        return 'product';
                    }
                    return 'webpage';
                }
            }
        } else {
            aysa_debug_log("AYSA Entity Detection: No post ID found for URL: $url");
        }
        
        // Category/Tag detection
        if (strpos($url, '/category/') !== false) {
            $slug = basename(wp_parse_url($url, PHP_URL_PATH));
            $term = get_term_by('slug', $slug, 'category');
            if ($term) {
                return 'category'; // Will map to CollectionPage in frontend
            }
        }
        
        if (strpos($url, '/tag/') !== false) {
            $slug = basename(wp_parse_url($url, PHP_URL_PATH));
            $term = get_term_by('slug', $slug, 'post_tag');
            if ($term) {
                return 'category'; // Will map to CollectionPage in frontend
            }
        }
        
        // Check for WooCommerce product categories
        if (strpos($url, '/product-category/') !== false) {
            $slug = basename(wp_parse_url($url, PHP_URL_PATH));
            $term = get_term_by('slug', $slug, 'product_cat');
            if ($term) {
                return 'category'; // Product category is still a collection
            }
        }
        
        // Check for author pages
        if (strpos($url, '/author/') !== false) {
            return 'webpage'; // Author pages are webpages
        }
        
        // Check for archive pages
        if (strpos($url, '/archive/') !== false || 
            preg_match('/\/\d{4}\//', $url) || 
            preg_match('/\/\d{4}\/\d{2}\//', $url)) {
            return 'category'; // Archive pages are collections
        }
        
        // Default fallback
        return 'webpage';
    }
    
    /**
     * Get WooCommerce product data for schema
     */
    private static function get_woocommerce_product_data($post_id) {
        if (!class_exists('WooCommerce') || !function_exists('wc_get_product')) {
            return array();
        }
        
        $product = wc_get_product($post_id);
        if (!$product) {
            aysa_debug_log("AYSA WooCommerce: Could not get product for post ID: $post_id");
            return array();
        }
        
        // Get price
        $price = $product->get_price();
        $regular_price = $product->get_regular_price();
        $sale_price = $product->get_sale_price();
        
        // Use sale price if available, otherwise regular price
        $final_price = !empty($sale_price) ? $sale_price : $regular_price;
        if (empty($final_price)) {
            $final_price = $price;
        }
        
        // Get currency
        $currency = get_woocommerce_currency();
        
        // Get SKU
        $sku = $product->get_sku();
        if (empty($sku)) {
            $sku = 'PRODUCT-' . $post_id;
        }
        
        // Get availability
        $availability = "https://schema.org/OutOfStock";
        if ($product->is_in_stock()) {
            $availability = "https://schema.org/InStock";
        }
        
        // Get product images
        $image_ids = $product->get_gallery_image_ids();
        $featured_image = get_the_post_thumbnail_url($post_id, 'full');
        $images = array();
        
        if ($featured_image) {
            $images[] = $featured_image;
        }
        
        foreach ($image_ids as $image_id) {
            $image_url = wp_get_attachment_image_url($image_id, 'full');
            if ($image_url) {
                $images[] = $image_url;
            }
        }
        
        $product_data = array(
            'productPrice' => number_format((float)$final_price, 2, '.', ''),
            'productCurrency' => $currency,
            'productSku' => $sku,
            'productAvailability' => $availability,
            'productImages' => $images
        );
        
        aysa_debug_log("AYSA WooCommerce Product Data Extracted: " . json_encode($product_data));
        
        return $product_data;
    }
    
    /**
     * AJAX handler for checking FAQ content in a page
     */
    public static function check_faq_content_ajax() {
        if (!self::verify_schema_ajax_access()) {
            return;
        }
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
        if (!wp_verify_nonce($nonce, 'aysa_schema_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        $url = sanitize_url(isset($_POST['url']) ? (string) wp_unslash($_POST['url']) : '');
        if (empty($url)) {
            wp_send_json_error('URL is required');
            return;
        }
        
        // Fetch page content
        $response = wp_remote_get($url, array(
            'timeout' => 30,
            'user-agent' => 'AYSA Schema Checker/1.0'
        ));
        
        if (is_wp_error($response)) {
            wp_send_json_error('Failed to fetch page content: ' . $response->get_error_message());
            return;
        }
        
        $body = wp_remote_retrieve_body($response);
        if (empty($body)) {
            wp_send_json_error('Empty page content');
            return;
        }
        
        // Parse HTML and look for FAQ microdata
        $dom = new DOMDocument();
        libxml_use_internal_errors(true);
        $dom->loadHTML($body);
        libxml_clear_errors();
        
        $xpath = new DOMXPath($dom);
        
        // Look for FAQ microdata
        $faqElements = $xpath->query('//div[@itemscope and @itemtype="https://schema.org/FAQPage"]');
        
        if ($faqElements->length === 0) {
            wp_send_json_success(array(
                'found' => false,
                'count' => 0
            ));
            return;
        }
        
        // Count questions
        $questionCount = 0;
        foreach ($faqElements as $faqElement) {
            $questions = $xpath->query('.//div[@itemscope and @itemtype="https://schema.org/Question"]', $faqElement);
            $questionCount += $questions->length;
        }
        
            wp_send_json_success(array(
                'found' => true,
                'count' => $questionCount
            ));
        }
        
        /**
         * AJAX handler to disable ALL schema markup (manual + automatic)
         */
        public static function disable_all_schemas_ajax() {
            if (!self::verify_schema_ajax_access()) {
                return;
            }
            // Verify nonce
            $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
            if (!wp_verify_nonce($nonce, 'aysa_schema_nonce')) {
                wp_send_json_error('Invalid nonce');
                return;
            }
            
            try {
                // 1. Delete all manual schemas from database
                global $wpdb;
                $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $deleted_manual = $wpdb->query("DELETE FROM $table_name");
                
                aysa_debug_log("AYSA Disable All Schemas: Deleted $deleted_manual manual schemas from database");
                
                // 2. Disable automatic schema generation by setting a global option
                update_option('aysa_disable_all_schema_output', true);
                
                // 3. Remove wp_head hooks that might generate schema
                // This will take effect on next page load
                remove_action('wp_head', array('AYSA_Schema_Manager', 'output_schema_markup'));
                
                // 4. If AYSA_Structured_Data class exists, disable it too
                if (class_exists('AYSA_Structured_Data')) {
                    remove_action('wp_head', array('AYSA_Structured_Data', 'detect_existing_schema'), 0);
                    remove_action('wp_head', array('AYSA_Structured_Data', 'final_schema_check'), 1000);
                }
                
                // 5. Clear any schema-related options/cache
                delete_option('aysa_schema_blacklist'); // Clear blacklist since we're disabling everything
                
                wp_send_json_success(array(
                    'message' => 'All schema markup has been disabled',
                    'manual_schemas_deleted' => $deleted_manual,
                    'automatic_schema_disabled' => true,
                    'hooks_removed' => true
                ));
                
            } catch (Exception $e) {
                aysa_debug_log("AYSA Disable All Schemas Error: " . $e->getMessage());
                wp_send_json_error('Failed to disable schemas: ' . $e->getMessage());
            }
        }
        
        /**
         * AJAX handler to physically clean LD+JSON schema elements from a page
         */
        public static function clean_page_schemas_ajax() {
            if (!self::verify_schema_ajax_access()) {
                return;
            }
            // Verify nonce
            $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : \x27\x27;
            if (!wp_verify_nonce($nonce, 'aysa_schema_nonce')) {
                wp_send_json_error('Invalid nonce');
                return;
            }

            $url = sanitize_url(isset($_POST['url']) ? (string) wp_unslash($_POST['url']) : '');
            if (empty($url)) {
                wp_send_json_error('Missing URL');
                return;
            }
            
            try {
                $cleaned_count = 0;
                
                // Get post ID from URL
                $post_id = url_to_postid($url);
                
                if ($post_id) {
                    // Get post content
                    $post = get_post($post_id);
                    if ($post) {
                        $content = $post->post_content;
                        $original_content = $content;
                        
                        // Remove all LD+JSON schema scripts from content
                        $pattern = '/<script[^>]*type=["\']application\/ld\+json["\'][^>]*>.*?<\/script>/is';
                        $content = preg_replace($pattern, '', $content);
                        
                        // Count how many were removed
                        preg_match_all($pattern, $original_content, $matches);
                        $cleaned_count = count($matches[0]);
                        
                        // Update post content if schemas were found and removed
                        if ($cleaned_count > 0 && $content !== $original_content) {
                            $updated_post = array(
                                'ID' => $post_id,
                                'post_content' => $content
                            );
                            wp_update_post($updated_post);
                            
                            aysa_debug_log("AYSA Schema Cleaner: Removed $cleaned_count LD+JSON schemas from post ID $post_id ($url)");
                        }
                    }
                }
                
                // Also remove from our database
                global $wpdb;
                $table_name = esc_sql($wpdb->prefix . 'aysa_schemas');
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $db_deleted = $wpdb->delete($table_name, array('url' => $url));
                
                if ($db_deleted > 0) {
                    $cleaned_count += $db_deleted;
                    aysa_debug_log("AYSA Schema Cleaner: Removed $db_deleted database schemas for $url");
                }
                
                // Also fetch the page via HTTP and scan for any remaining schemas
                $response = wp_remote_get($url, array('timeout' => 10));
                if (!is_wp_error($response)) {
                    $body = wp_remote_retrieve_body($response);
                    if ($body) {
                        // Count remaining schemas in live page
                        preg_match_all('/<script[^>]*type=["\']application\/ld\+json["\'][^>]*>.*?<\/script>/is', $body, $live_matches);
                        $live_schema_count = count($live_matches[0]);
                        
                        if ($live_schema_count > 0) {
                            aysa_debug_log("AYSA Schema Cleaner: WARNING - $live_schema_count LD+JSON schemas still found in live page: $url");
                        }
                    }
                }
                
                wp_send_json_success(array(
                    'url' => $url,
                    'cleaned' => $cleaned_count,
                    'post_id' => $post_id,
                    'message' => $cleaned_count > 0 ? "Removed $cleaned_count schema elements" : "No schemas found to remove"
                ));
                
            } catch (Exception $e) {
                aysa_debug_log("AYSA Schema Cleaner Error for $url: " . $e->getMessage());
                wp_send_json_error('Failed to clean schemas: ' . $e->getMessage());
            }
        }
    }

// Initialize the Schema Manager
AysaSchemaManager::init();
