<?php

/**
 * Meta REST API Controller
 *
 * Handles meta tags (title, description, canonical) via REST API.
 *
 * @package ProRank\SEO\Core\RestApi
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\RestApi;

use ProRank\SEO\Core\FocusKeywords;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

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


/**
 * MetaController class
 */


class MetaController extends BaseController {
    
    /**
     * Constructor
     */
    public function __construct() {
        $this->rest_base = 'meta';
    }
    
    /**
     * Register routes
     *
     * @return void
     */
    public function register_routes(): void {
        // GET/POST /meta/{post_id}
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<post_id>\d+)',
            [
                [
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_item'],
                    'permission_callback' => [$this, 'check_permission_edit_posts'],
                    'args'                => [
                        'post_id' => [
                            'description' => __('Post ID', 'prorank-seo'),
                            'type'        => 'integer',
                            'required'    => true,
                            'minimum'     => 1,
                        ],
                    ],
                ],
                [
                    'methods'             => \WP_REST_Server::EDITABLE,
                    'callback'            => [$this, 'update_item'],
                    'permission_callback' => [$this, 'check_permission_edit_posts'],
                    'args'                => array_merge(
                        [
                            'post_id' => [
                                'description' => __('Post ID', 'prorank-seo'),
                                'type'        => 'integer',
                                'required'    => true,
                                'minimum'     => 1,
                            ],
                        ],
                        $this->get_meta_schema()
                    ),
                ],
            ]
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<post_id>\d+)/schema-preview',
            [
                [
                    'methods'             => \WP_REST_Server::EDITABLE,
                    'callback'            => [$this, 'get_schema_preview'],
                    'permission_callback' => [$this, 'check_permission_edit_posts'],
                    'args'                => [
                        'post_id' => [
                            'description' => __('Post ID', 'prorank-seo'),
                            'type'        => 'integer',
                            'required'    => true,
                            'minimum'     => 1,
                        ],
                        'schema_data' => [
                            'description' => __('Temporary schema data used for preview generation.', 'prorank-seo'),
                            'type'        => 'object',
                            'required'    => false,
                        ],
                    ],
                ],
            ]
        );
    }
    
    /**
     * Get meta data for a post
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function get_item($request) {
        return $this->try_callback(function() use ($request) {
            $post_id = (int) $request->get_param('post_id');
            
            // Verify post exists
            $post = get_post($post_id);
            if (!$post) {
                return $this->get_error_response(
                    __('Post not found.', 'prorank-seo'),
                    404,
                    'post_not_found'
                );
            }
            
            // Get meta values with defaults
            $seo_title = get_post_meta($post_id, '_prorank_seo_title', true);
            $seo_description = get_post_meta($post_id, '_prorank_seo_description', true);
            $canonical_url = get_post_meta($post_id, '_prorank_seo_canonical_url', true);
            $meta_robots = get_post_meta($post_id, '_prorank_meta_robots', true);
            $focus_keywords = FocusKeywords::get($post_id);
            $focus_keyword  = FocusKeywords::get_primary($post_id);
            $sitemap_priority_override = (string) get_post_meta($post_id, '_prorank_sitemap_priority_override', true);
            
            // Get social meta values
            $og_title = get_post_meta($post_id, '_prorank_og_title', true);
            $og_description = get_post_meta($post_id, '_prorank_og_description', true);
            $og_image_id = get_post_meta($post_id, '_prorank_og_image_id', true);
            $twitter_title = get_post_meta($post_id, '_prorank_twitter_title', true);
            $twitter_description = get_post_meta($post_id, '_prorank_twitter_description', true);
            $twitter_image_id = get_post_meta($post_id, '_prorank_twitter_image_id', true);
            $twitter_card_type = get_post_meta($post_id, '_prorank_twitter_card_type', true);
            
            // Get schema data
            $schema_data = get_post_meta($post_id, '_prorank_schema_data', true);
            
            // Provide defaults
            if (empty($seo_title)) {
                $seo_title = '';
            }
            if (empty($seo_description)) {
                $seo_description = '';
            }
            if (empty($canonical_url)) {
                $canonical_url = '';
            }
            if (!is_array($meta_robots)) {
                $meta_robots = [];
            }
            if (empty($focus_keyword)) {
                $focus_keyword = '';
            }
            
            // Social defaults
            if (empty($og_title)) {
                $og_title = '';
            }
            if (empty($og_description)) {
                $og_description = '';
            }
            if (empty($og_image_id)) {
                $og_image_id = 0;
            }
            if (empty($twitter_title)) {
                $twitter_title = '';
            }
            if (empty($twitter_description)) {
                $twitter_description = '';
            }
            if (empty($twitter_image_id)) {
                $twitter_image_id = 0;
            }
            if (empty($twitter_card_type)) {
                $twitter_card_type = 'summary';
            }
            if (!is_array($schema_data)) {
                $schema_data = [];
            }
            
            // Get image URLs
            $og_image_url = '';
            if ($og_image_id) {
                $og_image_url = wp_get_attachment_url((int) $og_image_id);
            }
            
            $twitter_image_url = '';
            if ($twitter_image_id) {
                $twitter_image_url = wp_get_attachment_url((int) $twitter_image_id);
            }
            
            // Get featured image as fallback
            $featured_image_url = '';
            $featured_image_id = get_post_thumbnail_id($post_id);
            if ($featured_image_id) {
                $featured_image_url = wp_get_attachment_url($featured_image_id);
            }
            
            $default_title = ( 'auto-draft' === $post->post_status ) ? '' : (string) $post->post_title;
            $default_url   = ( 'auto-draft' === $post->post_status ) ? '' : (string) get_permalink( $post_id );
            $default_description = ( 'auto-draft' === $post->post_status ) ? '' : $this->get_preview_description( $post );

            return $this->get_success_response([
                'seo_title' => $seo_title,
                'seo_description' => $seo_description,
                'canonical_url' => $canonical_url,
                'meta_robots' => $meta_robots,
                'focus_keyword' => $focus_keyword,
                'focus_keywords' => $focus_keywords,
                'focus_keywords_limit' => FocusKeywords::get_limit(),
                'sitemap_priority_override' => $sitemap_priority_override,
                // Social fields
                'og_title' => $og_title,
                'og_description' => $og_description,
                'og_image_id' => (int) $og_image_id,
                'og_image_url' => $og_image_url ?: '',
                'twitter_title' => $twitter_title,
                'twitter_description' => $twitter_description,
                'twitter_image_id' => (int) $twitter_image_id,
                'twitter_image_url' => $twitter_image_url ?: '',
                'twitter_card_type' => $twitter_card_type,
                // Schema data
                'schema_data' => $schema_data,
                // Internal linking data
                'is_pillar_content' => (bool) get_post_meta($post_id, '_prorank_is_pillar_content', true),
                'link_stats' => $this->get_post_link_stats($post_id, $post->post_content),
                'defaults' => [
                    'title' => $default_title,
                    'url' => $default_url,
                    'description' => $default_description,
                ],
                'featured_image_url' => $featured_image_url ?: '',
            ]);
        });
    }

    /**
     * Generate a schema preview for the editor meta box.
     *
     * This endpoint is intentionally attached to MetaController because the editor
     * schema tab is always available. That keeps the preview route stable even when
     * optional schema modules are disabled or not initialized on the current request.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function get_schema_preview($request) {
        return $this->try_callback(function() use ($request) {
            $post_id = (int) $request->get_param('post_id');
            $post = get_post($post_id);

            if (!$post) {
                return $this->get_error_response(
                    __('Post not found.', 'prorank-seo'),
                    404,
                    'post_not_found'
                );
            }

            $schema_data = $request->get_param('schema_data');
            if (!is_array($schema_data) || empty($schema_data)) {
                $schema_data = get_post_meta($post_id, '_prorank_schema_data', true);
            }
            if (!is_array($schema_data)) {
                $schema_data = [];
            }

            $preview = $this->build_schema_preview_payload($post, $schema_data);

            return $this->get_success_response([
                'preview' => wp_json_encode($preview, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            ]);
        });
    }
    
    /**
     * Update meta data for a post
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function update_item($request) {
        return $this->try_callback(function() use ($request) {
            $post_id = (int) $request->get_param('post_id');
            
            // Verify post exists
            $post = get_post($post_id);
            if (!$post) {
                return $this->get_error_response(
                    __('Post not found.', 'prorank-seo'),
                    404,
                    'post_not_found'
                );
            }
            
            $updated = [];
            
            // Update SEO title
            if ($request->has_param('seo_title')) {
                $seo_title = sanitize_text_field($request->get_param('seo_title'));
                $result = update_post_meta($post_id, '_prorank_seo_title', $seo_title);
                if ($result === false && $seo_title !== get_post_meta($post_id, '_prorank_seo_title', true)) {
                    return $this->get_error_response(
                        __('Failed to update SEO title.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['seo_title'] = $seo_title;
            }
            
            // Update meta description
            if ($request->has_param('seo_description')) {
                $seo_description = sanitize_textarea_field($request->get_param('seo_description'));
                $result = update_post_meta($post_id, '_prorank_seo_description', $seo_description);
                if ($result === false && $seo_description !== get_post_meta($post_id, '_prorank_seo_description', true)) {
                    return $this->get_error_response(
                        __('Failed to update meta description.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['seo_description'] = $seo_description;
            }
            
            // Update canonical URL
            if ($request->has_param('canonical_url')) {
                $canonical_url = esc_url_raw($request->get_param('canonical_url'));
                $result = update_post_meta($post_id, '_prorank_seo_canonical_url', $canonical_url);
                if ($result === false && $canonical_url !== get_post_meta($post_id, '_prorank_seo_canonical_url', true)) {
                    return $this->get_error_response(
                        __('Failed to update canonical URL.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['canonical_url'] = $canonical_url;
            }
            
            // Update meta robots
            if ($request->has_param('meta_robots')) {
                $meta_robots = $request->get_param('meta_robots');
                if (!is_array($meta_robots)) {
                    $meta_robots = [];
                }
                // Sanitize array values
                $meta_robots = array_map('sanitize_key', $meta_robots);
                $meta_robots = array_values(array_filter($meta_robots)); // Remove empty values + normalize keys

                $current_meta_robots = get_post_meta($post_id, '_prorank_meta_robots', true);
                if (!is_array($current_meta_robots)) {
                    $current_meta_robots = empty($current_meta_robots) ? [] : (array) $current_meta_robots;
                }
                $current_meta_robots = array_values(array_filter(array_map('sanitize_key', $current_meta_robots)));
                
                $result = update_post_meta($post_id, '_prorank_meta_robots', $meta_robots);
                if ($result === false && $meta_robots !== $current_meta_robots) {
                    return $this->get_error_response(
                        __('Failed to update meta robots.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['meta_robots'] = $meta_robots;
            }

            // Update focus keywords (multi-keyword or single-keyword backward compat).
            if ($request->has_param('focus_keywords')) {
                $raw_keywords = $request->get_param('focus_keywords');
                $result = FocusKeywords::save($post_id, $raw_keywords);
                $updated['focus_keywords'] = $result['saved'];
                $updated['focus_keyword']  = $result['primary'];
                if ($result['truncated']) {
                    $updated['focus_keywords_truncated'] = true;
                    $updated['focus_keywords_limit'] = FocusKeywords::get_limit();
                }
            } elseif ($request->has_param('focus_keyword')) {
                // Legacy single-keyword write: wrap in array format.
                $single = sanitize_text_field($request->get_param('focus_keyword'));
                $keywords_input = $single !== '' ? [['keyword' => $single, 'primary' => true]] : [];

                // Preserve existing secondary keywords when only the primary is updated.
                if ($single !== '') {
                    $existing = FocusKeywords::get($post_id);
                    $secondary = array_filter($existing, function($kw) { return empty($kw['primary']); });
                    $keywords_input = array_merge($keywords_input, array_values($secondary));
                }

                $result = FocusKeywords::save($post_id, $keywords_input);
                $updated['focus_keyword']  = $result['primary'];
                $updated['focus_keywords'] = $result['saved'];
            }

            if ($request->has_param('sitemap_priority_override')) {
                $sitemap_priority_override = $this->sanitize_sitemap_priority_override(
                    $request->get_param('sitemap_priority_override')
                );

                if ($sitemap_priority_override === null) {
                    return $this->get_error_response(
                        __('Sitemap priority override must be between 0.0 and 1.0.', 'prorank-seo'),
                        400,
                        'invalid_sitemap_priority'
                    );
                }

                $current_priority = (string) get_post_meta($post_id, '_prorank_sitemap_priority_override', true);

                if ($sitemap_priority_override === '') {
                    delete_post_meta($post_id, '_prorank_sitemap_priority_override');
                } else {
                    $result = update_post_meta($post_id, '_prorank_sitemap_priority_override', $sitemap_priority_override);
                    if ($result === false && $current_priority !== $sitemap_priority_override) {
                        return $this->get_error_response(
                            __('Failed to update sitemap priority override.', 'prorank-seo'),
                            500,
                            'update_failed'
                        );
                    }
                }

                if ($current_priority !== $sitemap_priority_override) {
                    \ProRank\SEO\Modules\Indexing\Sitemaps\BaseSitemapProvider::invalidate_all_caches();
                }

                $updated['sitemap_priority_override'] = $sitemap_priority_override;
            }
            
            // Update Open Graph title
            if ($request->has_param('og_title')) {
                $og_title = sanitize_text_field($request->get_param('og_title'));
                $result = update_post_meta($post_id, '_prorank_og_title', $og_title);
                if ($result === false && $og_title !== get_post_meta($post_id, '_prorank_og_title', true)) {
                    return $this->get_error_response(
                        __('Failed to update Open Graph title.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['og_title'] = $og_title;
            }
            
            // Update Open Graph description
            if ($request->has_param('og_description')) {
                $og_description = sanitize_textarea_field($request->get_param('og_description'));
                $result = update_post_meta($post_id, '_prorank_og_description', $og_description);
                if ($result === false && $og_description !== get_post_meta($post_id, '_prorank_og_description', true)) {
                    return $this->get_error_response(
                        __('Failed to update Open Graph description.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['og_description'] = $og_description;
            }
            
            // Update Open Graph image
            if ($request->has_param('og_image_id')) {
                $og_image_id = (int) $request->get_param('og_image_id');
                $result = update_post_meta($post_id, '_prorank_og_image_id', $og_image_id);
                if ($result === false && $og_image_id !== (int) get_post_meta($post_id, '_prorank_og_image_id', true)) {
                    return $this->get_error_response(
                        __('Failed to update Open Graph image.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['og_image_id'] = $og_image_id;
            }
            
            // Update Twitter title
            if ($request->has_param('twitter_title')) {
                $twitter_title = sanitize_text_field($request->get_param('twitter_title'));
                $result = update_post_meta($post_id, '_prorank_twitter_title', $twitter_title);
                if ($result === false && $twitter_title !== get_post_meta($post_id, '_prorank_twitter_title', true)) {
                    return $this->get_error_response(
                        __('Failed to update Twitter title.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['twitter_title'] = $twitter_title;
            }
            
            // Update Twitter description
            if ($request->has_param('twitter_description')) {
                $twitter_description = sanitize_textarea_field($request->get_param('twitter_description'));
                $result = update_post_meta($post_id, '_prorank_twitter_description', $twitter_description);
                if ($result === false && $twitter_description !== get_post_meta($post_id, '_prorank_twitter_description', true)) {
                    return $this->get_error_response(
                        __('Failed to update Twitter description.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['twitter_description'] = $twitter_description;
            }
            
            // Update Twitter image
            if ($request->has_param('twitter_image_id')) {
                $twitter_image_id = (int) $request->get_param('twitter_image_id');
                $result = update_post_meta($post_id, '_prorank_twitter_image_id', $twitter_image_id);
                if ($result === false && $twitter_image_id !== (int) get_post_meta($post_id, '_prorank_twitter_image_id', true)) {
                    return $this->get_error_response(
                        __('Failed to update Twitter image.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['twitter_image_id'] = $twitter_image_id;
            }
            
            // Update Twitter card type
            if ($request->has_param('twitter_card_type')) {
                $twitter_card_type = sanitize_key($request->get_param('twitter_card_type'));
                // Validate card type
                $valid_types = ['summary', 'summary_large_image', 'app', 'player'];
                if (!in_array($twitter_card_type, $valid_types, true)) {
                    $twitter_card_type = 'summary';
                }
                $result = update_post_meta($post_id, '_prorank_twitter_card_type', $twitter_card_type);
                if ($result === false && $twitter_card_type !== get_post_meta($post_id, '_prorank_twitter_card_type', true)) {
                    return $this->get_error_response(
                        __('Failed to update Twitter card type.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['twitter_card_type'] = $twitter_card_type;
            }
            
            // Update schema data
            if ($request->has_param('schema_data')) {
                $schema_data = $request->get_param('schema_data');
                if (!is_array($schema_data)) {
                    $schema_data = [];
                }
                
                // Sanitize schema data
                if (isset($schema_data['type'])) {
                    $schema_data['type'] = sanitize_text_field($schema_data['type']);
                }
                if (isset($schema_data['properties']) && is_array($schema_data['properties'])) {
                    // Recursively sanitize properties
                    $schema_data['properties'] = $this->sanitize_schema_properties($schema_data['properties']);
                }
                if (isset($schema_data['custom_json'])) {
                    // Validate JSON
                    if (!empty($schema_data['custom_json'])) {
                        $decoded = json_decode($schema_data['custom_json'], true);
                        if (json_last_error() !== JSON_ERROR_NONE) {
                            return $this->get_error_response(
                                __('Invalid JSON in custom schema.', 'prorank-seo'),
                                400,
                                'invalid_json'
                            );
                        }
                    }
                }
                
                $result = update_post_meta($post_id, '_prorank_schema_data', $schema_data);
                $current_schema_data = get_post_meta($post_id, '_prorank_schema_data', true);
                if (!is_array($current_schema_data)) {
                    $current_schema_data = [];
                }
                if ($result === false && $schema_data != $current_schema_data) {
                    return $this->get_error_response(
                        __('Failed to update schema data.', 'prorank-seo'),
                        500,
                        'update_failed'
                    );
                }
                $updated['schema_data'] = $schema_data;
            }

            // Update pillar content flag
            if ($request->has_param('is_pillar_content')) {
                $is_pillar = (bool) $request->get_param('is_pillar_content');
                update_post_meta($post_id, '_prorank_is_pillar_content', $is_pillar ? '1' : '');
                $updated['is_pillar_content'] = $is_pillar;
            }

            return $this->get_success_response(
                $updated,
                __('Meta tags updated successfully.', 'prorank-seo')
            );
        });
    }

    /**
     * Build a JSON-LD preview payload from saved or unsaved schema data.
     *
     * @param \WP_Post $post Post object.
     * @param array    $schema_data Schema data array.
     * @return array
     */
    private function build_schema_preview_payload(\WP_Post $post, array $schema_data): array {
        $schema_type = sanitize_text_field($schema_data['type'] ?? 'Article');

        if ($schema_type === 'Custom') {
            $decoded = json_decode((string) ($schema_data['custom_json'] ?? ''), true);
            if (is_array($decoded)) {
                if (!isset($decoded['@context'])) {
                    $decoded = ['@context' => 'https://schema.org'] + $decoded;
                }

                return $decoded;
            }
        }

        $post_url = get_permalink($post);
        $preview = [
            '@context' => 'https://schema.org',
            '@type' => $schema_type,
            'headline' => get_the_title($post),
            'url' => $post_url,
            'datePublished' => get_the_date('c', $post),
            'dateModified' => get_the_modified_date('c', $post),
        ];

        $description = $this->get_preview_description($post);
        if (!empty($description)) {
            $preview['description'] = $description;
        }

        $author_id = (int) $post->post_author;
        if ($author_id > 0) {
            $preview['author'] = [
                '@type' => 'Person',
                'name' => get_the_author_meta('display_name', $author_id),
                'url' => get_author_posts_url($author_id),
            ];
        }

        $thumbnail_id = get_post_thumbnail_id($post);
        if ($thumbnail_id) {
            $image_data = wp_get_attachment_image_src($thumbnail_id, 'full');
            if ($image_data) {
                $preview['image'] = [
                    '@type' => 'ImageObject',
                    'url' => $image_data[0],
                    'width' => $image_data[1],
                    'height' => $image_data[2],
                ];
            }
        }

        if (!empty($schema_data['properties']) && is_array($schema_data['properties'])) {
            foreach ($schema_data['properties'] as $key => $value) {
                $property_key = sanitize_text_field((string) $key);
                if ($property_key === '' || $value === '' || $value === null) {
                    continue;
                }

                $preview[$property_key] = is_scalar($value) ? $value : wp_json_encode($value);
            }
        }

        return $preview;
    }

    private function get_preview_description(\WP_Post $post): string {
        $excerpt = trim((string) get_the_excerpt($post));
        if ($excerpt !== '' && !$this->is_placeholder_preview_description($excerpt)) {
            return wp_strip_all_tags($excerpt);
        }

        $content = preg_replace('/\s+/u', ' ', wp_strip_all_tags((string) $post->post_content));
        $content = is_string($content) ? trim($content) : '';

        if ($content === '' || $this->is_placeholder_preview_description($content)) {
            return '';
        }

        if (function_exists('mb_substr')) {
            return mb_substr($content, 0, 220);
        }

        return substr($content, 0, 220);
    }

    private function is_placeholder_preview_description(string $description): bool {
        $normalized = strtolower(trim(preg_replace('/\s+/u', ' ', wp_strip_all_tags($description))));

        if ($normalized === '') {
            return true;
        }

        return (bool) preg_match(
            '/^(welcome to wordpress\.\s*)?this is your first post\.\s*edit or delete it,\s*then start writing!?$/',
            $normalized
        );
    }
    
    /**
     * Sanitize schema properties recursively
     *
     * @param array $properties Schema properties to sanitize.
     * @return array
     */
    private function sanitize_schema_properties(array $properties): array {
        $sanitized = [];
        
        foreach ($properties as $key => $value) {
            $key = sanitize_key($key);
            
            if (is_array($value)) {
                $sanitized[$key] = $this->sanitize_schema_properties($value);
            } elseif (is_string($value)) {
                // Check if it's a URL
                if (filter_var($value, FILTER_VALIDATE_URL)) {
                    $sanitized[$key] = esc_url_raw($value);
                } else {
                    $sanitized[$key] = sanitize_textarea_field($value);
                }
            } elseif (is_numeric($value)) {
                $sanitized[$key] = $value;
            } elseif (is_bool($value)) {
                $sanitized[$key] = $value;
            }
        }
        
        return $sanitized;
    }

    /**
     * Sanitize the sitemap priority override input.
     *
     * Empty values clear the override. Numeric values must be between 0.0 and
     * 1.0 inclusive.
     *
     * @param mixed $value Raw request value.
     * @return string|null
     */
    private function sanitize_sitemap_priority_override($value): ?string {
        if ($value === null) {
            return '';
        }

        if (is_string($value)) {
            $value = trim($value);
        }

        if ($value === '') {
            return '';
        }

        if (!is_numeric($value)) {
            return null;
        }

        $priority = round((float) $value, 1);
        if ($priority < 0.0 || $priority > 1.0) {
            return null;
        }

        return number_format($priority, 1, '.', '');
    }
    
    /**
     * Get meta fields schema
     *
     * @return array
     */
    private function get_meta_schema(): array {
        return [
            'seo_title' => [
                'description'       => __('SEO title for the post.', 'prorank-seo'),
                'type'              => 'string',
                'required'          => false,
                'sanitize_callback' => 'sanitize_text_field',
                'maxLength'         => 60, // Recommended max length
            ],
            'seo_description' => [
                'description'       => __('Meta description for the post.', 'prorank-seo'),
                'type'              => 'string',
                'required'          => false,
                'sanitize_callback' => 'sanitize_textarea_field',
                'maxLength'         => 160, // Recommended max length
            ],
            'canonical_url' => [
                'description'       => __('Canonical URL for the post.', 'prorank-seo'),
                'type'              => 'string',
                'format'            => 'uri',
                'required'          => false,
                'sanitize_callback' => 'esc_url_raw',
            ],
            'meta_robots' => [
                'description' => __('Meta robots directives.', 'prorank-seo'),
                'type'        => 'array',
                'required'    => false,
                'items'       => [
                    'type' => 'string',
                    'enum' => ['noindex', 'nofollow', 'noarchive', 'nosnippet', 'noimageindex'],
                ],
            ],
            'focus_keyword' => [
                'description'       => __('Primary focus keyword for the post (backward compatible).', 'prorank-seo'),
                'type'              => 'string',
                'required'          => false,
                'sanitize_callback' => 'sanitize_text_field',
                'maxLength'         => 120,
            ],
            'focus_keywords' => [
                'description' => __('Focus keywords for the post (multi-keyword).', 'prorank-seo'),
                'type'        => 'array',
                'required'    => false,
                'items'       => [
                    'type'       => 'object',
                    'properties' => [
                        'keyword' => [
                            'type'      => 'string',
                            'maxLength' => 120,
                        ],
                        'primary' => [
                            'type'    => 'boolean',
                            'default' => false,
                        ],
                    ],
                ],
            ],
            'sitemap_priority_override' => [
                'description'       => __('Optional XML sitemap priority override for this post.', 'prorank-seo'),
                'type'              => 'string',
                'required'          => false,
                'sanitize_callback' => 'sanitize_text_field',
                'maxLength'         => 4,
            ],
            'og_title' => [
                'description'       => __('Open Graph title for social sharing.', 'prorank-seo'),
                'type'              => 'string',
                'required'          => false,
                'sanitize_callback' => 'sanitize_text_field',
                'maxLength'         => 80, // Facebook recommended
            ],
            'og_description' => [
                'description'       => __('Open Graph description for social sharing.', 'prorank-seo'),
                'type'              => 'string',
                'required'          => false,
                'sanitize_callback' => 'sanitize_textarea_field',
                'maxLength'         => 200, // Facebook recommended
            ],
            'og_image_id' => [
                'description' => __('Open Graph image attachment ID.', 'prorank-seo'),
                'type'        => 'integer',
                'required'    => false,
                'minimum'     => 0,
            ],
            'twitter_title' => [
                'description'       => __('Twitter title for social sharing.', 'prorank-seo'),
                'type'              => 'string',
                'required'          => false,
                'sanitize_callback' => 'sanitize_text_field',
                'maxLength'         => 70, // Twitter recommended
            ],
            'twitter_description' => [
                'description'       => __('Twitter description for social sharing.', 'prorank-seo'),
                'type'              => 'string',
                'required'          => false,
                'sanitize_callback' => 'sanitize_textarea_field',
                'maxLength'         => 200, // Twitter recommended
            ],
            'twitter_image_id' => [
                'description' => __('Twitter image attachment ID.', 'prorank-seo'),
                'type'        => 'integer',
                'required'    => false,
                'minimum'     => 0,
            ],
            'twitter_card_type' => [
                'description' => __('Twitter card type.', 'prorank-seo'),
                'type'        => 'string',
                'required'    => false,
                'enum'        => ['summary', 'summary_large_image', 'app', 'player'],
                'default'     => 'summary',
            ],
            'schema_data' => [
                'description' => __('Schema.org structured data configuration.', 'prorank-seo'),
                'type'        => 'object',
                'required'    => false,
                'properties'  => [
                    'type' => [
                        'type' => 'string',
                        'description' => __('Primary schema type.', 'prorank-seo'),
                    ],
                    'properties' => [
                        'type' => 'object',
                        'description' => __('Schema type-specific properties.', 'prorank-seo'),
                    ],
                    'custom_json' => [
                        'type' => 'string',
                        'description' => __('Custom JSON-LD markup.', 'prorank-seo'),
                    ],
                ],
            ],
            'is_pillar_content' => [
                'description' => __('Mark this content as pillar/cornerstone content for internal linking.', 'prorank-seo'),
                'type'        => 'boolean',
                'required'    => false,
                'default'     => false,
            ],
        ];
    }

    /**
     * Get link statistics for a post
     *
     * @param int    $post_id Post ID.
     * @param string $content Post content.
     * @return array Link statistics.
     */
    private function get_post_link_stats(int $post_id, string $content): array {
        // Count internal and external links
        $internal_links = 0;
        $external_links = 0;
        $anchor_texts = [];
        $site_url = home_url();

        // Use DOMDocument for reliable parsing
        if (!empty($content)) {
            $dom = new \DOMDocument();
            libxml_use_internal_errors(true);
            $dom->loadHTML('<?xml encoding="UTF-8">' . $content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
            libxml_clear_errors();

            $links = $dom->getElementsByTagName('a');
            foreach ($links as $link) {
                $href = $link->getAttribute('href');
                $anchor = trim($link->textContent);

                if (empty($href) || strpos($href, '#') === 0 || strpos($href, 'mailto:') === 0) {
                    continue;
                }

                // Check if internal or external
                if (strpos($href, $site_url) === 0 || strpos($href, '/') === 0) {
                    $internal_links++;
                    if (!empty($anchor)) {
                        $anchor_texts[] = $anchor;
                    }
                } else {
                    $external_links++;
                }
            }
        }

        $total_links = $internal_links + $external_links;

        // Calculate anchor text diversity (unique anchors / total internal)
        $unique_anchors = count(array_unique($anchor_texts));
        $anchor_diversity = $internal_links > 0 ? round(($unique_anchors / $internal_links) * 100) : 100;

        // Warnings
        $warnings = [];
        if ($total_links > 150) {
            $warnings[] = __('Too many links on this page (150+ may dilute link equity).', 'prorank-seo');
        } elseif ($total_links > 100) {
            $warnings[] = __('High link count. Consider reducing to under 100 links.', 'prorank-seo');
        }

        if ($anchor_diversity < 50 && $internal_links > 3) {
            $warnings[] = __('Low anchor text diversity. Vary your anchor text for better SEO.', 'prorank-seo');
        }

        return [
            'internal_links' => $internal_links,
            'external_links' => $external_links,
            'total_links' => $total_links,
            'anchor_diversity' => $anchor_diversity,
            'warnings' => $warnings,
        ];
    }
}
