<?php

/**
 * Titles & Meta Module
 *
 * @package ProRank\SEO\Modules\OnPageSEO
 * @since   0.1.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\OnPageSEO;

use ProRank\SEO\Core\FocusKeywords;
use ProRank\SEO\Modules\BaseModule;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

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


/**
 * TitlesMetaModule class
 */


class TitlesMetaModule extends BaseModule {
    /**
     * Canonical options used by old/new settings UIs.
     */
    private const OPTION_PRIMARY = 'prorank_titles_meta_settings';
    private const OPTION_COMPAT = 'prorank_seo_titles_meta';
    
    /**
     * Module slug
     *
     * @var string
     */
    protected string $slug = 'titles-meta';
    
    /**
     * Module name
     *
     * @var string
     */
    protected string $name = 'Titles & Meta';
    
    /**
     * Module description
     *
     * @var string
     */
    protected string $description = 'Manage page titles and meta descriptions';
    
    /**
     * Required tier
     *
     * @var string
     */
    protected string $feature_tier = 'free';
    
    /**
     * Parent module slug
     *
     * @var string|null
     */
    protected ?string $parent_slug = 'on-page-seo';
    
    /**
     * Default settings
     *
     * @var array
     */
    private array $default_settings = [
        // General settings
        'enable_title_meta' => true,
        'title_separator' => '-',
        'capitalize_titles' => false,
        'homepage_title_format' => 'sitename_tagline',
        'homepage_title_type' => 'auto',
        'homepage_custom_title' => '',
        'homepage_meta_type' => 'auto',
        'homepage_auto_meta_source' => 'tagline',
        'homepage_custom_meta' => '',
        'static_homepage_title_type' => 'auto',
        'static_homepage_custom_title' => '',
        'static_homepage_meta_type' => 'auto',
        'static_homepage_custom_meta' => '',
        'homepage_robots' => '',
        'blog_page_title_type' => 'auto',
        'blog_page_title_format' => 'page_title_sitename',
        'blog_page_custom_title' => '',
        'blog_page_meta_type' => 'auto',
        'blog_page_custom_meta' => '',
        // Legacy aliases (kept for backward compatibility with frontend/importers)
        'custom_homepage_title' => '',
        'homepage_title' => '',
        'homepage_description' => '',
        'homepage_schema_type' => 'WebSite',
        'homepage_robots_noindex' => false,
        'homepage_robots_nofollow' => false,
        
        // Title templates
        'post_title_template' => '{title} {sep} {sitename}',
        'post_meta_template' => '{excerpt}',
        'page_title_template' => '{title} {sep} {sitename}',
        'page_meta_template' => '{excerpt}',
        'archive_title_template' => '{term} Archives {sep} {sitename}',
        'archive_meta_template' => 'Browse {term} posts on {sitename}',
        'author_title_template' => '{name} {sep} {sitename}',
        'author_description_template' => 'Posts by {name} on {sitename}',
        'search_title_template' => '{search_query} {page} {sep} {sitename}',
        'search_meta_template' => 'Search results for {search_query} on {sitename}',
        '404_title_template' => 'Page Not Found {sep} {sitename}',
        '404_description_template' => 'The page you are looking for could not be found.',
        'date_archive_title_template' => '{date} {sep} {sitename}',
        'author_archive_title' => '{name} {sep} {sitename}',
        'author_archive_description' => 'Posts by {name} on {sitename}',
        'search_title' => '{search_query} {page} {sep} {sitename}',
        'error_404_title' => 'Page Not Found {sep} {sitename}',
        'date_archive_title' => '{date} {sep} {sitename}',
        
        // Character limits
        'title_length_warning' => 60,
        'meta_length_warning' => 160,
        
        // Default robots meta
        'robots_default_index' => true,
        'robots_default_follow' => true,
        'robots_default_archive' => true,
        'robots_default_imageindex' => true,
        'robots_default_snippet' => true,
        'robots_default_noarchive' => false,
        'robots_default_noimageindex' => false,
        'robots_default_nosnippet' => false,
        
        // Advanced robots meta
        'robots_max_snippet' => -1,
        'robots_max_video_preview' => -1,
        'robots_max_image_preview' => 'large',
        
        // Special settings - Miscellaneous Pages
        'noindex_empty_archives' => true,
        'noindex_search_pages' => true,
        'noindex_search' => true,
        'noindex_subpages' => false,
        'noindex_paginated_single' => false,
        'noindex_paginated_singles' => false, // Alias for consistency
        'noindex_paginated_pages' => false,   // Archive pagination
        'noindex_password_protected' => true,
        'noindex_attachments' => true,        // Attachment pages noindex
        'noindex_date_archives' => false,     // Date archive noindex
        'noindex_author_archives' => false,   // Author archive noindex
        'noindex_format_archives' => true,    // Post format archive noindex
        'disable_date_archives' => false,
        'disable_author_archives' => false,
        'author_base' => 'author',
        'author_robots_meta' => 'default',
        'author_robots_index' => true,
        'author_robots_follow' => true,
        'author_robots_archive' => true,
        'author_robots_imageindex' => true,
        'author_robots_snippet' => true,
        'author_max_snippet' => -1,
        'author_max_video_preview' => -1,
        'author_max_image_preview' => 'large',
        'author_slack_enhanced' => false,
        'author_seo_meta_box' => true,
        'author_robots_noindex' => false,
        'author_robots_nofollow' => false,
        'search_robots_noindex' => true,
        'date_archive_robots_noindex' => false,

        // Canonical URL settings
        'canonical_include_pagination' => false, // Include page number in canonical URL
        
        // Social Meta
        'facebook_page_url' => '',
        'facebook_authorship' => '',
        'facebook_admin' => '',
        'facebook_app' => '',
        'facebook_secret' => '',
        'twitter_username' => '',
        'instagram_url' => '',
        'youtube_url' => '',
        'linkedin_url' => '',
        'pinterest_url' => '',
        'additional_profiles' => '',
        'og_default_image' => '',
        'og_default_image_url' => '',
        'twitter_card_type' => 'summary_large_image',
        
        // Pro features
        'ai_title_suggestions' => false,
        'dynamic_variables' => false,
        'bulk_editor' => false,
    ];
    
    /**
     * Initialize hooks
     *
     * @return void
     */
    public function init_hooks(): void {
        // Register REST routes
        add_action('rest_api_init', [$this, 'register_rest_routes']);
        add_action('init', [$this, 'apply_author_base'], 0);
        
        // Filter title and meta output
        add_filter('document_title_separator', [$this, 'filter_title_separator']);
        add_filter('document_title_parts', [$this, 'filter_title_parts']);
        
        // Apply title capitalization if enabled
        if ($this->get_setting('capitalize_titles')) {
            add_filter('wp_title', [$this, 'capitalize_title'], 100);
            add_filter('pre_get_document_title', [$this, 'capitalize_title'], 100);
        }
        
        // Handle empty archives noindex
        if ($this->get_setting('noindex_empty_archives')) {
            add_action('wp_head', [$this, 'handle_empty_archives_noindex'], 0);
        }
        
        // Handle archive redirects
        if ($this->get_setting('disable_date_archives')) {
            add_action('template_redirect', [$this, 'redirect_date_archives']);
        }
        if ($this->get_setting('disable_author_archives')) {
            add_action('template_redirect', [$this, 'redirect_author_archives']);
        }
        
        // Handle noindex for special pages
        add_action('wp_head', [$this, 'handle_special_pages_noindex'], 1);
        
        // Initialize settings on activation
        add_action('init', [$this, 'maybe_init_settings']);

        // Taxonomy term edit screen SEO fields
        add_action('admin_init', [$this, 'register_taxonomy_seo_fields']);

        // Keep taxonomy term robots compatible with the core wp_robots pipeline.
        add_filter('wp_robots', [$this, 'filter_taxonomy_robots_meta']);
    }
    
    /**
     * Register REST routes
     *
     * @return void
     */
    public function register_rest_routes(): void {
        // Main settings endpoint
        register_rest_route('prorank-seo/v1', '/settings/titles-meta', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_settings_endpoint'],
            'permission_callback' => [$this, 'check_permission'],
        ]);
        
        register_rest_route('prorank-seo/v1', '/settings/titles-meta', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'update_settings_endpoint'],
            'permission_callback' => [$this, 'check_permission'],
        ]);
        
        // General settings endpoint (for frontend compatibility)
        register_rest_route('prorank-seo/v1', '/settings/titles_meta_general', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_general_settings_endpoint'],
            'permission_callback' => [$this, 'check_permission'],
        ]);
        
        register_rest_route('prorank-seo/v1', '/settings/titles_meta_general', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'update_general_settings_endpoint'],
            'permission_callback' => [$this, 'check_permission'],
        ]);
        
        // Homepage settings endpoint
        register_rest_route('prorank-seo/v1', '/settings/titles_meta_homepage', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_homepage_settings_endpoint'],
            'permission_callback' => [$this, 'check_permission'],
        ]);
        
        register_rest_route('prorank-seo/v1', '/settings/titles_meta_homepage', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'update_homepage_settings_endpoint'],
            'permission_callback' => [$this, 'check_permission'],
        ]);
        
        // Site info endpoint
        register_rest_route('prorank-seo/v1', '/settings/site_info', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_site_info_endpoint'],
            'permission_callback' => [$this, 'check_permission'],
        ]);
        
        // Bulk edit endpoints
        register_rest_route('prorank-seo/v1', '/titles-meta/bulk-edit', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_bulk_edit_data'],
            'permission_callback' => [$this, 'check_permission'],
        ]);
        
        register_rest_route('prorank-seo/v1', '/titles-meta/bulk-edit', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'save_bulk_edit_data'],
            'permission_callback' => [$this, 'check_permission'],
        ]);
    }
    
    /**
     * Check REST permission
     *
     * @return bool
     */
    public function check_permission(): bool {
        return current_user_can('manage_options');
    }
    
    /**
     * Get settings endpoint
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function get_settings_endpoint(WP_REST_Request $request) {
        $settings = $this->get_all_settings();
        return rest_ensure_response($settings);
    }
    
    /**
     * Update settings endpoint
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function update_settings_endpoint(WP_REST_Request $request) {
        $settings = $request->get_json_params();
        $current_settings = $this->get_all_settings();

        if (is_array($settings) && !empty($settings['__reset_defaults'])) {
            $default_settings = $this->sync_titles_meta_aliases(
                $this->sync_homepage_legacy_aliases($this->default_settings)
            );
            $saved = $this->persist_settings($default_settings);

            if ($saved) {
                $this->maybe_flush_author_rewrites($current_settings, $default_settings);

                return rest_ensure_response([
                    'success' => true,
                    'message' => __('Settings reset to defaults', 'prorank-seo'),
                    'settings' => $this->get_all_settings(),
                ]);
            }

            return new WP_Error(
                'reset_failed',
                __('Failed to reset settings', 'prorank-seo'),
                ['status' => 500]
            );
        }

        if (empty($settings)) {
            return new WP_Error(
                'invalid_settings',
                __('Invalid settings data', 'prorank-seo'),
                ['status' => 400]
            );
        }
        
        // Validate and sanitize settings
        $validated_settings = $this->validate_settings($settings);
        $validated_settings = array_merge($current_settings, $validated_settings);
        $validated_settings = $this->sync_homepage_legacy_aliases($validated_settings);
        $validated_settings = $this->sync_titles_meta_aliases($validated_settings);
        
        // Save settings
        $saved = $this->persist_settings($validated_settings);
        
        if (!$saved && $validated_settings === $current_settings) {
            // Settings unchanged
            $saved = true;
        }
        
        if ($saved) {
            $this->maybe_flush_author_rewrites($current_settings, $validated_settings);

            return rest_ensure_response([
                'success' => true,
                'message' => __('Settings saved successfully', 'prorank-seo'),
                'settings' => $validated_settings,
            ]);
        }
        
        return new WP_Error(
            'save_failed',
            __('Failed to save settings', 'prorank-seo'),
            ['status' => 500]
        );
    }
    
    /**
     * Get all settings
     *
     * @return array
     */
    public function get_all_settings(): array {
        $settings = $this->load_synced_settings();
        return wp_parse_args($settings, $this->default_settings);
    }
    
    /**
     * Get a specific setting
     *
     * @param string $key Setting key.
     * @param mixed $default Default value.
     * @return mixed
     */
    public function get_setting(string $key, $default = null) {
        $settings = $this->get_all_settings();
        return isset($settings[$key]) ? $settings[$key] : ($default ?: ($this->default_settings[$key] ?? null));
    }
    
    /**
     * Validate settings
     *
     * @param array $settings Settings to validate.
     * @return array Validated settings.
     */
    private function validate_settings(array $settings): array {
        $validated = [];
        $boolean_settings = [
            'enable_title_meta',
            'capitalize_titles',
            'robots_default_index',
            'robots_default_follow',
            'robots_default_archive',
            'robots_default_imageindex',
            'robots_default_snippet',
            'noindex_empty_archives',
            'noindex_paginated_pages',
            'noindex_search_pages',
            'noindex_search',
            'noindex_subpages',
            'noindex_paginated_single',
            'noindex_paginated_singles',
            'noindex_password_protected',
            'noindex_date_archives',
            'noindex_author_archives',
            'noindex_format_archives',
            'disable_date_archives',
            'disable_author_archives',
            'canonical_include_pagination',
            'ai_title_suggestions',
            'dynamic_variables',
            'bulk_editor',
            'author_robots_index',
            'author_robots_follow',
            'author_robots_archive',
            'author_robots_imageindex',
            'author_robots_snippet',
            'author_slack_enhanced',
            'author_seo_meta_box',
            'homepage_robots_noindex',
            'homepage_robots_nofollow',
        ];
        $integer_settings = [
            'title_length_warning',
            'meta_length_warning',
            'robots_max_snippet',
            'robots_max_video_preview',
            'author_max_snippet',
            'author_max_video_preview',
        ];
        
        foreach ($settings as $key => $value) {
            if ($key === '__reset_defaults' || $key === 'use_custom_separator' || $key === 'custom_separator') {
                continue;
            }

            if (strpos($key, 'post_type_') === 0 && is_array($value)) {
                $validated[$key] = $this->sanitize_post_type_settings($value);
                continue;
            }

            if (strpos($key, 'taxonomy_') === 0 && is_array($value)) {
                $validated[$key] = $this->sanitize_taxonomy_settings($value);
                continue;
            }

            if (in_array($key, $boolean_settings, true)) {
                $validated[$key] = (bool) $value;
                continue;
            }

            if (in_array($key, $integer_settings, true)) {
                $validated[$key] = (int) $value;
                continue;
            }

            switch ($key) {
                // String settings
                case 'title_separator':
                case 'homepage_title_format':
                case 'custom_homepage_title':
                case 'homepage_title':
                case 'homepage_title_type':
                case 'homepage_custom_title':
                case 'homepage_meta_type':
                case 'homepage_auto_meta_source':
                case 'homepage_custom_meta':
                case 'static_homepage_title_type':
                case 'static_homepage_custom_title':
                case 'static_homepage_meta_type':
                case 'static_homepage_custom_meta':
                case 'blog_page_title_type':
                case 'blog_page_title_format':
                case 'blog_page_custom_title':
                case 'blog_page_meta_type':
                case 'blog_page_custom_meta':
                case 'homepage_robots':
                case 'homepage_description':
                case 'author_base':
                case 'author_robots_meta':
                case 'author_archive_title':
                case 'author_archive_description':
                case 'search_title':
                case 'error_404_title':
                case 'date_archive_title':
                case 'post_title_template':
                case 'post_meta_template':
                case 'page_title_template':
                case 'page_meta_template':
                case 'archive_title_template':
                case 'archive_meta_template':
                case 'author_title_template':
                case 'author_description_template':
                case 'author_meta_template':
                case 'search_title_template':
                case 'search_meta_template':
                case '404_title_template':
                case '404_description_template':
                case '404_meta_template':
                case 'date_archive_title_template':
                case 'robots_max_image_preview':
                case 'twitter_card_type':
                case 'twitter_username':
                case 'og_default_image':
                case 'og_default_image_url':
                case 'facebook_page_url':
                case 'facebook_authorship':
                case 'facebook_admin':
                case 'facebook_app':
                case 'facebook_secret':
                case 'instagram_url':
                case 'youtube_url':
                case 'linkedin_url':
                case 'pinterest_url':
                case 'additional_profiles':
                    $validated[$key] = sanitize_text_field($value);
                    break;
                    
                default:
                    if (is_string($value) || is_numeric($value)) {
                        $validated[$key] = sanitize_text_field((string) $value);
                    }
                    break;
            }
        }
        
        return $validated;
    }

    private function sanitize_post_type_settings(array $settings): array {
        $validated = [];
        $boolean_keys = ['show_meta_box', 'bulk_editing', 'noindex', 'nofollow', 'noarchive', 'noimageindex', 'nosnippet'];
        $string_keys = ['single_title', 'single_description', 'archive_title', 'archive_description', 'schema_type', 'article_type', 'robots'];

        foreach ($settings as $key => $value) {
            if (in_array($key, $boolean_keys, true)) {
                $validated[$key] = (bool) $value;
            } elseif (in_array($key, $string_keys, true)) {
                $validated[$key] = sanitize_text_field((string) $value);
            }
        }

        return $validated;
    }

    private function sanitize_taxonomy_settings(array $settings): array {
        $validated = [];
        $boolean_keys = ['show_seo_controls', 'remove_schema', 'noindex', 'nofollow'];
        $string_keys = ['archive_title', 'archive_description', 'robots'];

        foreach ($settings as $key => $value) {
            if (in_array($key, $boolean_keys, true)) {
                $validated[$key] = (bool) $value;
            } elseif (in_array($key, $string_keys, true)) {
                $validated[$key] = sanitize_text_field((string) $value);
            }
        }

        return $validated;
    }
    
    /**
     * Register SEO fields on taxonomy term edit screens.
     */
    public function register_taxonomy_seo_fields(): void {
        $taxonomies = get_taxonomies(['public' => true], 'names');
        foreach ($taxonomies as $taxonomy) {
            $tax_key = 'taxonomy_' . $taxonomy;
            $tax_settings = $this->get_titles_meta_setting($tax_key);
            if (is_array($tax_settings) && isset($tax_settings['show_seo_controls']) && $tax_settings['show_seo_controls'] === false) {
                continue;
            }
            add_action("{$taxonomy}_edit_form_fields", [$this, 'render_term_seo_fields'], 100, 2);
            add_action("edited_{$taxonomy}", [$this, 'save_term_seo_fields'], 10, 2);
        }
    }

    /**
     * Render SEO fields on the term edit screen.
     *
     * @param \WP_Term $term     Term being edited.
     * @param string   $taxonomy Taxonomy slug.
     */
    public function render_term_seo_fields($term, $taxonomy): void {
        $seo_title = get_term_meta($term->term_id, '_prorank_seo_title', true);
        $seo_desc  = get_term_meta($term->term_id, '_prorank_seo_description', true);
        $canonical = get_term_meta($term->term_id, '_prorank_seo_canonical', true);
        $noindex   = get_term_meta($term->term_id, '_prorank_seo_noindex', true);
        $nofollow  = get_term_meta($term->term_id, '_prorank_seo_nofollow', true);

        wp_nonce_field('prorank_term_seo', '_prorank_term_seo_nonce');
        ?>
        <tr class="form-field">
            <th colspan="2"><h2 style="margin:0;padding:12px 0 0;border-top:1px solid #ddd"><?php esc_html_e('ProRank SEO Settings', 'prorank-seo'); ?></h2></th>
        </tr>
        <tr class="form-field">
            <th scope="row"><label for="prorank-seo-title"><?php esc_html_e('SEO Title', 'prorank-seo'); ?></label></th>
            <td>
                <input type="text" id="prorank-seo-title" name="prorank_seo_title" value="<?php echo esc_attr($seo_title); ?>" class="large-text" />
                <p class="description"><?php esc_html_e('Custom title for search engines. Leave empty to use the default template.', 'prorank-seo'); ?></p>
            </td>
        </tr>
        <tr class="form-field">
            <th scope="row"><label for="prorank-seo-description"><?php esc_html_e('Meta Description', 'prorank-seo'); ?></label></th>
            <td>
                <textarea id="prorank-seo-description" name="prorank_seo_description" rows="3" class="large-text"><?php echo esc_textarea($seo_desc); ?></textarea>
                <p class="description"><?php esc_html_e('Custom meta description for this term archive.', 'prorank-seo'); ?></p>
            </td>
        </tr>
        <tr class="form-field">
            <th scope="row"><label for="prorank-seo-canonical"><?php esc_html_e('Canonical URL', 'prorank-seo'); ?></label></th>
            <td>
                <input type="url" id="prorank-seo-canonical" name="prorank_seo_canonical" value="<?php echo esc_attr($canonical); ?>" class="large-text" />
                <p class="description"><?php esc_html_e('Override the canonical URL. Leave empty for default.', 'prorank-seo'); ?></p>
            </td>
        </tr>
        <tr class="form-field">
            <th scope="row"><?php esc_html_e('Robots Meta', 'prorank-seo'); ?></th>
            <td>
                <label style="display:inline-block;margin-right:16px">
                    <input type="checkbox" name="prorank_seo_noindex" value="1" <?php checked($noindex, '1'); ?> />
                    <?php esc_html_e('Noindex', 'prorank-seo'); ?>
                </label>
                <label style="display:inline-block">
                    <input type="checkbox" name="prorank_seo_nofollow" value="1" <?php checked($nofollow, '1'); ?> />
                    <?php esc_html_e('Nofollow', 'prorank-seo'); ?>
                </label>
                <p class="description"><?php esc_html_e('Override the default robots meta for this term.', 'prorank-seo'); ?></p>
            </td>
        </tr>
        <?php
    }

    /**
     * Save term SEO fields.
     *
     * @param int $term_id  Term ID.
     * @param int $tt_id    Term taxonomy ID.
     */
    public function save_term_seo_fields($term_id, $tt_id): void {
        if (!isset($_POST['_prorank_term_seo_nonce']) || !wp_verify_nonce(sanitize_key($_POST['_prorank_term_seo_nonce']), 'prorank_term_seo')) {
            return;
        }
        if (!current_user_can('manage_categories')) {
            return;
        }

        $fields = [
            'prorank_seo_title'       => '_prorank_seo_title',
            'prorank_seo_description' => '_prorank_seo_description',
            'prorank_seo_canonical'   => '_prorank_seo_canonical',
        ];

        foreach ($fields as $post_key => $meta_key) {
            $value = isset($_POST[$post_key]) ? sanitize_text_field(wp_unslash($_POST[$post_key])) : '';
            if ($value !== '') {
                update_term_meta($term_id, $meta_key, $value);
            } else {
                delete_term_meta($term_id, $meta_key);
            }
        }

        // Checkboxes
        $noindex  = isset($_POST['prorank_seo_noindex']) ? '1' : '';
        $nofollow = isset($_POST['prorank_seo_nofollow']) ? '1' : '';

        if ($noindex) {
            update_term_meta($term_id, '_prorank_seo_noindex', '1');
        } else {
            delete_term_meta($term_id, '_prorank_seo_noindex');
        }

        if ($nofollow) {
            update_term_meta($term_id, '_prorank_seo_nofollow', '1');
        } else {
            delete_term_meta($term_id, '_prorank_seo_nofollow');
        }
    }

    /**
     * Get a specific titles-meta setting by key.
     *
     * @param string $key Setting key.
     * @return mixed
     */
    private function get_titles_meta_setting(string $key) {
        $settings = get_option(self::OPTION_PRIMARY, []);
        return $settings[$key] ?? null;
    }

    /**
     * Apply per-term robots overrides to the core wp_robots pipeline.
     */
    public function filter_taxonomy_robots_meta(array $robots): array {
        if (!is_category() && !is_tag() && !is_tax()) {
            return $robots;
        }
        $term = get_queried_object();
        if (!$term instanceof \WP_Term) {
            return $robots;
        }
        $noindex  = get_term_meta($term->term_id, '_prorank_seo_noindex', true);
        $nofollow = get_term_meta($term->term_id, '_prorank_seo_nofollow', true);
        if ($noindex) {
            $robots['noindex'] = true;
            unset($robots['index']);
        }
        if ($nofollow) {
            $robots['nofollow'] = true;
            unset($robots['follow']);
        }

        return $robots;
    }

    /**
     * Output per-term meta description on taxonomy archive pages.
     */
    public function output_taxonomy_meta_description(): void {
        if (!is_category() && !is_tag() && !is_tax()) {
            return;
        }
        $term = get_queried_object();
        if (!$term instanceof \WP_Term) {
            return;
        }
        $description = get_term_meta($term->term_id, '_prorank_seo_description', true);
        if (empty($description)) {
            $description = $term->description;
        }
        if (!empty($description)) {
            $description = wp_strip_all_tags($description);
            $description = mb_substr($description, 0, 160);
            echo '<meta name="description" content="' . esc_attr($description) . '" />' . "\n";
        }
    }

    public function maybe_init_settings(): void {
        $primary = get_option(self::OPTION_PRIMARY, null);
        $compat = get_option(self::OPTION_COMPAT, null);

        if (!is_array($primary) && is_array($compat)) {
            update_option(self::OPTION_PRIMARY, $compat, false);
            $primary = $compat;
        }

        if (!is_array($primary)) {
            $primary = $this->default_settings;
            update_option(self::OPTION_PRIMARY, $primary, false);
        }

        if (!is_array($compat)) {
            update_option(self::OPTION_COMPAT, $primary, false);
        }
    }
    
    /**
     * Filter title separator
     *
     * @param string $separator Current separator.
     * @return string
     */
    public function filter_title_separator(string $separator): string {
        $custom_separator = $this->get_setting('title_separator');
        return !empty($custom_separator) ? ' ' . $custom_separator . ' ' : $separator;
    }
    
    /**
     * Filter title parts
     *
     * @param array $title_parts Title parts.
     * @return array
     */
    public function filter_title_parts(array $title_parts): array {
        // Apply templates based on current page type
        if (is_front_page()) {
            // For static front page, use the page title if no custom SEO title is set
            $front_page_id = get_option('page_on_front');
            if ($front_page_id) {
                $seo_title = get_post_meta($front_page_id, '_prorank_seo_title', true);
                if (!empty($seo_title)) {
                    $title_parts['title'] = $seo_title;
                } elseif (empty($title_parts['title'])) {
                    $title_parts['title'] = get_the_title($front_page_id);
                }
            } elseif (empty($title_parts['title'])) {
                $title_parts['title'] = get_bloginfo('name');
            }
        } elseif (is_home() && !is_front_page()) {
            $template = $this->get_setting('archive_title_template');
            if ($template) {
                $title_parts['title'] = $this->process_template($template, 'archive');
            }
        } elseif (is_singular('post')) {
            $template = $this->get_setting('post_title_template');
            if ($template && empty(get_post_meta(get_the_ID(), '_prorank_seo_title', true))) {
                $title_parts['title'] = $this->process_template($template, 'post');
            }
        } elseif (is_page()) {
            $template = $this->get_setting('page_title_template');
            if ($template && empty(get_post_meta(get_the_ID(), '_prorank_seo_title', true))) {
                $title_parts['title'] = $this->process_template($template, 'page');
            }
        } elseif (is_author()) {
            $template = $this->get_setting('author_title_template');
            if ($template) {
                $title_parts['title'] = $this->process_template($template, 'author');
            }
        } elseif (is_search()) {
            $template = $this->get_setting('search_title_template');
            if ($template) {
                $title_parts['title'] = $this->process_template($template, 'search');
            }
        } elseif (is_404()) {
            $template = $this->get_setting('404_title_template');
            if ($template) {
                $title_parts['title'] = $this->process_template($template, '404');
            }
        } elseif (is_category() || is_tag() || is_tax()) {
            $term = get_queried_object();
            if ($term instanceof \WP_Term) {
                $custom_title = get_term_meta($term->term_id, '_prorank_seo_title', true);
                if (!empty($custom_title)) {
                    $title_parts['title'] = $custom_title;
                } else {
                    $tax_key = 'taxonomy_' . $term->taxonomy;
                    $tax_settings = $this->get_titles_meta_setting($tax_key);
                    $template = $tax_settings['archive_title'] ?? $this->get_setting('archive_title_template');
                    if ($template) {
                        $title_parts['title'] = $this->process_template($template, 'archive');
                    }
                }
            }
        } elseif (is_archive()) {
            $template = $this->get_setting('archive_title_template');
            if ($template) {
                $title_parts['title'] = $this->process_template($template, 'archive');
            }
        }
        
        // Apply capitalization if enabled
        if ($this->get_setting('capitalize_titles') && isset($title_parts['title'])) {
            $title_parts['title'] = $this->capitalize_title($title_parts['title']);
        }
        
        return $title_parts;
    }
    
    /**
     * Process template with variables
     *
     * @param string $template Template string.
     * @param string $context Page context.
     * @return string
     */
    public function process_template(string $template, string $context = ''): string {
        // Get pagination info
        $page = '';
        $paged = get_query_var('paged', 0);
        if ($paged > 1) {
/* translators: %1$: value, %2$: value */
            $page = sprintf(__('Page %1$d of %2$d', 'prorank-seo'), $paged, $GLOBALS['wp_query']->max_num_pages);
        }
        
        // Get current date/time info
        $current_time = current_time('timestamp');
        
        $replacements = [
            // Separator variables
            '{sep}' => $this->get_setting('title_separator', '|'),
            '{separator}' => $this->get_setting('title_separator', '|'),
            '%sep%' => $this->get_setting('title_separator', '|'),
            '%separator%' => $this->get_setting('title_separator', '|'),
            
            // Site variables
            '{sitename}' => get_bloginfo('name'),
            '%sitename%' => get_bloginfo('name'),
            '{site_name}' => get_bloginfo('name'),
            '%site_name%' => get_bloginfo('name'),
            '{tagline}' => get_bloginfo('description'),
            '%tagline%' => get_bloginfo('description'),
            '{site_tagline}' => get_bloginfo('description'),
            '%site_tagline%' => get_bloginfo('description'),
            '{site_url}' => home_url(),
            '%site_url%' => home_url(),
            
            // Date/Time variables
            '{currentyear}' => wp_date('Y', $current_time),
            '%currentyear%' => wp_date('Y', $current_time),
            '{currentmonth}' => wp_date('F', $current_time),
            '%currentmonth%' => wp_date('F', $current_time),
            '{currentmonth_num}' => wp_date('m', $current_time),
            '%currentmonth_num%' => wp_date('m', $current_time),
            '{currentday}' => wp_date('j', $current_time),
            '%currentday%' => wp_date('j', $current_time),
            '{currentdate}' => wp_date(get_option('date_format'), $current_time),
            '%currentdate%' => wp_date(get_option('date_format'), $current_time),
            '{currenttime}' => wp_date(get_option('time_format'), $current_time),
            '%currenttime%' => wp_date(get_option('time_format'), $current_time),
            
            // Pagination
            '{page}' => $page,
            '%page%' => $page,
            '{page_number}' => $paged > 0 ? $paged : '',
            '%page_number%' => $paged > 0 ? $paged : '',
        ];
        
        // Context-specific replacements
        switch ($context) {
            case 'post':
            case 'page':
                if (is_singular()) {
                    // Use get_queried_object() first as it's available earlier in the
                    // request lifecycle (pre_get_document_title runs before global $post is set)
                    $post = get_queried_object();
                    if (!$post instanceof \WP_Post) {
                        $post = get_post();
                    }
                    if ($post) {
                        $replacements['{title}'] = $post->post_title;
                        $replacements['%title%'] = $post->post_title;
                        $replacements['{excerpt}'] = wp_trim_words($post->post_excerpt ?: $post->post_content, 20);
                        $replacements['%excerpt%'] = wp_trim_words($post->post_excerpt ?: $post->post_content, 20);
                        $replacements['{author}'] = get_the_author_meta('display_name', $post->post_author);
                        $replacements['%author%'] = get_the_author_meta('display_name', $post->post_author);
                        $replacements['{date}'] = get_the_date('', $post);
                        $replacements['%date%'] = get_the_date('', $post);
                        $replacements['{modified}'] = get_the_modified_date('', $post);
                        $replacements['%modified%'] = get_the_modified_date('', $post);
                        
                        // SEO title and description if set
                        $seo_title = get_post_meta($post->ID, '_prorank_seo_title', true);
                        $seo_description = get_post_meta($post->ID, '_prorank_seo_description', true);
                        $replacements['%seo_title%'] = $seo_title ?: $post->post_title;
                        $replacements['%seo_description%'] = $seo_description ?: wp_trim_words($post->post_excerpt ?: $post->post_content, 20);
                        
                        // WooCommerce product variables
                        if (class_exists('WooCommerce') && $post->post_type === 'product') {
                            $product = wc_get_product($post->ID);
                            if ($product) {
                                $replacements['{price}'] = $product->get_price_html();
                                $replacements['%price%'] = $product->get_price_html();
                                $replacements['{sku}'] = $product->get_sku();
                                $replacements['%sku%'] = $product->get_sku();
                                $replacements['{stock_status}'] = $product->is_in_stock() ? __('In Stock', 'prorank-seo') : __('Out of Stock', 'prorank-seo');
                                $replacements['%stock_status%'] = $product->is_in_stock() ? __('In Stock', 'prorank-seo') : __('Out of Stock', 'prorank-seo');
                                
                                // Product categories
                                $categories = wp_get_post_terms($post->ID, 'product_cat', ['fields' => 'names']);
                                $replacements['{product_cat}'] = !empty($categories) ? implode(', ', $categories) : '';
                                $replacements['%product_cat%'] = !empty($categories) ? implode(', ', $categories) : '';
                                
                                // Product tags
                                $tags = wp_get_post_terms($post->ID, 'product_tag', ['fields' => 'names']);
                                $replacements['{product_tag}'] = !empty($tags) ? implode(', ', $tags) : '';
                                $replacements['%product_tag%'] = !empty($tags) ? implode(', ', $tags) : '';
                            }
                        }
                        
                        // Custom fields support
                        $replacements['{custom_field:*}'] = ''; // Placeholder for custom field pattern
                        $replacements['%custom_field:*%'] = ''; // Placeholder for custom field pattern
                    }
                }
                break;
                
            case 'archive':
                if (is_category() || is_tag() || is_tax()) {
                    $term = get_queried_object();
                    if ($term) {
                        $replacements['{term}'] = $term->name;
                        $replacements['%term%'] = $term->name;
                        $replacements['{title}'] = $term->name;
                        $replacements['%title%'] = $term->name;
                        $replacements['{term_description}'] = $term->description;
                        $replacements['%term_description%'] = $term->description;
                    }
                } elseif (is_date()) {
                    $date_format = '';
                    if (is_year()) {
                        $date_format = get_the_date('Y');
                    } elseif (is_month()) {
                        $date_format = get_the_date('F Y');
                    } elseif (is_day()) {
                        $date_format = get_the_date(get_option('date_format'));
                    }
                    $replacements['{date}'] = $date_format;
                    $replacements['%date%'] = $date_format;
                    $replacements['{title}'] = $date_format;
                    $replacements['%title%'] = $date_format;
                }
                break;
                
            case 'author':
                if (is_author()) {
                    $author = get_queried_object();
                    if ($author) {
                        $replacements['{author}'] = $author->display_name;
                        $replacements['%author%'] = $author->display_name;
                        $replacements['{name}'] = $author->display_name;
                        $replacements['%name%'] = $author->display_name;
                        $replacements['{title}'] = $author->display_name;
                        $replacements['%title%'] = $author->display_name;
                    }
                }
                break;
                
            case 'search':
                $search_query = get_search_query();
                $replacements['{searchphrase}'] = $search_query;
                $replacements['%searchphrase%'] = $search_query;
                $replacements['{search_query}'] = $search_query;
                $replacements['%search_query%'] = $search_query;
/* translators: %1$: value */
                $replacements['{title}'] = sprintf(__('Search Results for "%1$s"', 'prorank-seo'), $search_query);
/* translators: %1$: value */
                $replacements['%title%'] = sprintf(__('Search Results for "%1$s"', 'prorank-seo'), $search_query);
                break;
                
            case '404':
                $replacements['{title}'] = __('Page Not Found', 'prorank-seo');
                $replacements['%title%'] = __('Page Not Found', 'prorank-seo');
                break;
        }
        
        // Replace all variables
        $processed = str_replace(array_keys($replacements), array_values($replacements), $template);
        
        // Handle custom field replacements
        if (strpos($processed, 'custom_field:') !== false || strpos($processed, 'cf:') !== false) {
            // Pattern to match custom field variables
            $processed = preg_replace_callback(
                '/[\{%](?:custom_field|cf):([^}%]+)[\}%]/',
                function($matches) use ($context) {
                    $field_name = $matches[1];
                    $value = '';
                    
                    if (is_singular()) {
                        $post_id = get_the_ID();
                        $value = get_post_meta($post_id, $field_name, true);
                        
                        // If it's an array, convert to string
                        if (is_array($value)) {
                            $value = implode(', ', $value);
                        }
                    }
                    
                    return $value;
                },
                $processed
            );
        }
        
        // Clean up any remaining variables
        $processed = preg_replace('/[\{%][^}%]+[\}%]/', '', $processed);
        
        // Trim multiple spaces
        $processed = preg_replace('/\s+/', ' ', $processed);
        
        return trim($processed);
    }
    
    /**
     * Capitalize title
     *
     * @param string $title Title to capitalize.
     * @return string
     */
    public function capitalize_title(string $title): string {
        if (empty($title)) {
            return $title;
        }
        
        // Words that should not be capitalized (unless first or last word)
        $lowercase_words = ['a', 'an', 'the', 'and', 'but', 'or', 'for', 'nor', 'on', 'at', 'to', 'by', 'of', 'in', 'with', 'as'];
        
        $words = explode(' ', $title);
        $result = [];
        
        foreach ($words as $index => $word) {
            // Always capitalize first and last words
            if ($index === 0 || $index === count($words) - 1) {
                $result[] = ucfirst(strtolower($word));
            } elseif (in_array(strtolower($word), $lowercase_words, true)) {
                $result[] = strtolower($word);
            } else {
                $result[] = ucfirst(strtolower($word));
            }
        }
        
        return implode(' ', $result);
    }
    
    /**
     * Handle empty archives noindex
     *
     * @return void
     */
    public function handle_empty_archives_noindex(): void {
        if (!$this->get_setting('noindex_empty_archives')) {
            return;
        }
        
        // Check if we're on an archive page
        if (!is_archive() || is_author()) {
            return;
        }
        
        // Check if the archive has posts
        global $wp_query;
        if ($wp_query->found_posts === 0) {
            echo '<meta name="robots" content="noindex, follow" />' . "\n";
        }
    }
    
    /**
     * Get robots meta defaults
     *
     * @return array
     */
    public function get_robots_defaults(): array {
        return [
            'index' => $this->get_setting('robots_default_index', true),
            'follow' => $this->get_setting('robots_default_follow', true),
            'archive' => $this->get_setting('robots_default_archive', true),
            'imageindex' => $this->get_setting('robots_default_imageindex', true),
            'snippet' => $this->get_setting('robots_default_snippet', true),
            'max-snippet' => $this->get_setting('robots_max_snippet', -1),
            'max-video-preview' => $this->get_setting('robots_max_video_preview', -1),
            'max-image-preview' => $this->get_setting('robots_max_image_preview', 'large'),
        ];
    }
    
    /**
     * Get social defaults
     *
     * @return array
     */
    public function get_social_defaults(): array {
        return [
            'og_default_image' => $this->get_setting('og_default_image', ''),
            'og_default_image_url' => $this->get_setting('og_default_image_url', ''),
            'twitter_card_type' => $this->get_setting('twitter_card_type', 'summary_large_image'),
            'twitter_username' => $this->get_setting('twitter_username', ''),
        ];
    }
    
    /**
     * Redirect date archives if disabled
     *
     * @return void
     */
    public function redirect_date_archives(): void {
        if (!$this->get_setting('disable_date_archives')) {
            return;
        }
        
        if (is_date()) {
            wp_redirect(home_url(), 301);
            exit;
        }
    }
    
    /**
     * Redirect author archives if disabled
     *
     * @return void
     */
    public function redirect_author_archives(): void {
        if (!$this->get_setting('disable_author_archives')) {
            return;
        }
        
        if (is_author()) {
            $author_id = get_queried_object_id();
            $posts_url = get_author_posts_url($author_id);
            
            // If we have a redirect URL, use it, otherwise go to homepage
            $redirect_url = $this->get_setting('author_archives_redirect_url', '');
            if (empty($redirect_url)) {
                $redirect_url = home_url();
            } else {
                // Replace author variables in redirect URL
                $author = get_userdata($author_id);
                if ($author) {
                    $redirect_url = str_replace('%author_name%', $author->display_name, $redirect_url);
                    $redirect_url = str_replace('%author_id%', $author_id, $redirect_url);
                }
            }
            
            wp_redirect($redirect_url, 301);
            exit;
        }
    }
    
    /**
     * Handle special pages noindex
     *
     * @return void
     */
    public function handle_special_pages_noindex(): void {
        $add_noindex = false;
        
        // Check search pages
        if (is_search() && $this->get_setting('noindex_search_pages')) {
            $add_noindex = true;
        }
        
        // Check subpages of archives
        if (is_paged() && $this->get_setting('noindex_subpages')) {
            if (is_category() || is_tag() || is_tax() || is_author() || is_date() || is_home()) {
                $add_noindex = true;
            }
        }
        
        // Check paginated single posts
        if (is_singular() && get_query_var('page') > 1 && $this->get_setting('noindex_paginated_single')) {
            $add_noindex = true;
        }
        
        // Check password protected posts
        if (is_singular() && post_password_required() && $this->get_setting('noindex_password_protected')) {
            $add_noindex = true;
        }
        
        // Add noindex meta tag if needed
        if ($add_noindex) {
            add_filter('wp_robots', function($robots) {
                $robots['noindex'] = true;
                // Keep follow unless specifically set otherwise
                if (!isset($robots['follow'])) {
                    $robots['follow'] = true;
                }
                return $robots;
            });
        }
    }
    
    /**
     * Get general settings endpoint
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function get_general_settings_endpoint(WP_REST_Request $request) {
        $settings = $this->get_all_settings();
        
        // Filter to only general settings for this endpoint
        $general_settings = [
            'enable_title_meta' => $settings['enable_title_meta'] ?? true,
            'title_separator' => $settings['title_separator'] ?? '|',
            'title_length_warning' => $settings['title_length_warning'] ?? 60,
            'meta_length_warning' => $settings['meta_length_warning'] ?? 160,
            'capitalize_titles' => $settings['capitalize_titles'] ?? false,
            'og_default_image' => $settings['og_default_image'] ?? '',
            'twitter_card_type' => $settings['twitter_card_type'] ?? 'summary_large_image',
            'schema_organization_type' => $settings['schema_organization_type'] ?? 'Organization',
            'schema_organization_name' => $settings['schema_organization_name'] ?? '',
            'schema_organization_logo' => $settings['schema_organization_logo'] ?? '',
        ];
        
        return rest_ensure_response([
            'success' => true,
            'data' => $general_settings,
        ]);
    }
    
    /**
     * Update general settings endpoint
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function update_general_settings_endpoint(WP_REST_Request $request) {
        $input_settings = $request->get_json_params();
        
        if (empty($input_settings)) {
            return new WP_Error(
                'invalid_settings',
                __('Invalid settings data', 'prorank-seo'),
                ['status' => 400]
            );
        }
        
        // Get current settings and merge with new ones
        $current_settings = $this->get_all_settings();
        $updated_settings = array_merge($current_settings, $input_settings);
        
        // Save settings
        $saved = $this->persist_settings($updated_settings);
        
        if ($saved || $updated_settings === $current_settings) {
            return rest_ensure_response([
                'success' => true,
                'message' => __('Settings saved successfully', 'prorank-seo'),
                'data' => $input_settings,
            ]);
        }
        
        return new WP_Error(
            'save_failed',
            __('Failed to save settings', 'prorank-seo'),
            ['status' => 500]
        );
    }
    
    /**
     * Get homepage settings endpoint
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function get_homepage_settings_endpoint(WP_REST_Request $request) {
        $settings = $this->get_all_settings();

        $is_static_front = get_option('show_on_front', 'posts') === 'page';
        $legacy_home_title = (string) ($settings['homepage_title'] ?? $settings['custom_homepage_title'] ?? '');
        $legacy_home_meta = (string) ($settings['homepage_description'] ?? '');

        // Normalize old/legacy key names to modern homepage settings API shape.
        $homepage_title_type = (string) ($settings['homepage_title_type'] ?? ($legacy_home_title !== '' && !$is_static_front ? 'custom' : 'auto'));
        $homepage_custom_title = (string) ($settings['homepage_custom_title'] ?? (!$is_static_front ? $legacy_home_title : ''));
        $homepage_meta_type = (string) ($settings['homepage_meta_type'] ?? ($legacy_home_meta !== '' && !$is_static_front ? 'custom' : 'auto'));
        $homepage_custom_meta = (string) ($settings['homepage_custom_meta'] ?? (!$is_static_front ? $legacy_home_meta : ''));

        $static_homepage_title_type = (string) ($settings['static_homepage_title_type'] ?? ($legacy_home_title !== '' && $is_static_front ? 'custom' : 'auto'));
        $static_homepage_custom_title = (string) ($settings['static_homepage_custom_title'] ?? ($is_static_front ? $legacy_home_title : ''));
        $static_homepage_meta_type = (string) ($settings['static_homepage_meta_type'] ?? ($legacy_home_meta !== '' && $is_static_front ? 'custom' : 'auto'));
        $static_homepage_custom_meta = (string) ($settings['static_homepage_custom_meta'] ?? ($is_static_front ? $legacy_home_meta : ''));

        $homepage_settings = [
            'homepage_title_type' => $homepage_title_type,
            'homepage_title_format' => $settings['homepage_title_format'] ?? 'sitename_tagline',
            'homepage_custom_title' => $homepage_custom_title,
            'homepage_meta_type' => $homepage_meta_type,
            'homepage_auto_meta_source' => $settings['homepage_auto_meta_source'] ?? 'tagline',
            'homepage_custom_meta' => $homepage_custom_meta,
            'static_homepage_title_type' => $static_homepage_title_type,
            'static_homepage_custom_title' => $static_homepage_custom_title,
            'static_homepage_meta_type' => $static_homepage_meta_type,
            'static_homepage_custom_meta' => $static_homepage_custom_meta,
            'blog_page_title_type' => $settings['blog_page_title_type'] ?? 'auto',
            'blog_page_title_format' => $settings['blog_page_title_format'] ?? 'page_title_sitename',
            'blog_page_custom_title' => $settings['blog_page_custom_title'] ?? '',
            'blog_page_meta_type' => $settings['blog_page_meta_type'] ?? 'auto',
            'blog_page_custom_meta' => $settings['blog_page_custom_meta'] ?? '',
        ];
        
        return rest_ensure_response([
            'success' => true,
            'data' => $homepage_settings,
        ]);
    }
    
    /**
     * Update homepage settings endpoint
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function update_homepage_settings_endpoint(WP_REST_Request $request) {
        $input_settings = $request->get_json_params();
        
        if (empty($input_settings)) {
            return new WP_Error(
                'invalid_settings',
                __('Invalid settings data', 'prorank-seo'),
                ['status' => 400]
            );
        }
        
        // Get current settings and merge
        $current_settings = $this->get_all_settings();
        $updated_settings = array_merge($current_settings, $input_settings);
        $updated_settings = $this->sync_homepage_legacy_aliases($updated_settings);
        
        // Save settings
        $saved = $this->persist_settings($updated_settings);
        
        if ($saved || $updated_settings === $current_settings) {
            return rest_ensure_response([
                'success' => true,
                'message' => __('Settings saved successfully', 'prorank-seo'),
                'data' => $input_settings,
            ]);
        }
        
        return new WP_Error(
            'save_failed',
            __('Failed to save settings', 'prorank-seo'),
            ['status' => 500]
        );
    }

    /**
     * Keep legacy homepage keys synchronized with modern homepage settings keys.
     *
     * Frontend output and older importers still read `homepage_title` and
     * `homepage_description`, while the modern UI writes *_custom_* keys.
     *
     * @param array<string,mixed> $settings
     * @return array<string,mixed>
     */
    private function sync_homepage_legacy_aliases(array $settings): array {
        $is_static_front = get_option('show_on_front', 'posts') === 'page';

        if ($is_static_front) {
            $title_type = (string) ($settings['static_homepage_title_type'] ?? 'auto');
            $title_value = (string) ($settings['static_homepage_custom_title'] ?? '');
            $meta_type = (string) ($settings['static_homepage_meta_type'] ?? 'auto');
            $meta_value = (string) ($settings['static_homepage_custom_meta'] ?? '');
        } else {
            $title_type = (string) ($settings['homepage_title_type'] ?? 'auto');
            $title_value = (string) ($settings['homepage_custom_title'] ?? '');
            $meta_type = (string) ($settings['homepage_meta_type'] ?? 'auto');
            $meta_value = (string) ($settings['homepage_custom_meta'] ?? '');
        }

        if ($title_type === 'custom' && $title_value !== '') {
            $settings['homepage_title'] = $title_value;
        } elseif (!$is_static_front) {
            $settings['homepage_title'] = $this->map_homepage_title_format_to_template(
                (string) ($settings['homepage_title_format'] ?? 'sitename_tagline')
            );
        } else {
            // For static front pages in auto mode we intentionally keep this empty
            // and let WordPress use the page title naturally.
            $settings['homepage_title'] = '';
        }

        if ($meta_type === 'custom' && $meta_value !== '') {
            $settings['homepage_description'] = $meta_value;
        } elseif (!$is_static_front && (string) ($settings['homepage_auto_meta_source'] ?? 'tagline') === 'tagline') {
            $settings['homepage_description'] = '%tagline%';
        } else {
            // Keep empty so fallback generation can use post/page content where applicable.
            $settings['homepage_description'] = '';
        }

        // Maintain old alias key used by legacy paths.
        if (!empty($settings['homepage_custom_title'])) {
            $settings['custom_homepage_title'] = (string) $settings['homepage_custom_title'];
        }

        return $settings;
    }

    private function sync_titles_meta_aliases(array $settings): array {
        if (array_key_exists('noindex_search', $settings)) {
            $settings['noindex_search_pages'] = (bool) $settings['noindex_search'];
        } elseif (array_key_exists('noindex_search_pages', $settings)) {
            $settings['noindex_search'] = (bool) $settings['noindex_search_pages'];
        }

        if (array_key_exists('noindex_paginated_singles', $settings)) {
            $settings['noindex_paginated_single'] = (bool) $settings['noindex_paginated_singles'];
        } elseif (array_key_exists('noindex_paginated_single', $settings)) {
            $settings['noindex_paginated_singles'] = (bool) $settings['noindex_paginated_single'];
        }

        if (!empty($settings['author_archive_title'])) {
            $settings['author_title_template'] = (string) $settings['author_archive_title'];
        } elseif (!empty($settings['author_title_template']) && empty($settings['author_archive_title'])) {
            $settings['author_archive_title'] = (string) $settings['author_title_template'];
        }

        if (!empty($settings['author_archive_description'])) {
            $settings['author_description_template'] = (string) $settings['author_archive_description'];
            $settings['author_meta_template'] = (string) $settings['author_archive_description'];
        } elseif (!empty($settings['author_description_template']) && empty($settings['author_archive_description'])) {
            $settings['author_archive_description'] = (string) $settings['author_description_template'];
            $settings['author_meta_template'] = (string) $settings['author_description_template'];
        }

        if (!empty($settings['search_title'])) {
            $settings['search_title_template'] = (string) $settings['search_title'];
        } elseif (!empty($settings['search_title_template']) && empty($settings['search_title'])) {
            $settings['search_title'] = (string) $settings['search_title_template'];
        }

        if (!empty($settings['error_404_title'])) {
            $settings['404_title_template'] = (string) $settings['error_404_title'];
        } elseif (!empty($settings['404_title_template']) && empty($settings['error_404_title'])) {
            $settings['error_404_title'] = (string) $settings['404_title_template'];
        }

        if (!empty($settings['date_archive_title'])) {
            $settings['date_archive_title_template'] = (string) $settings['date_archive_title'];
        } elseif (!empty($settings['date_archive_title_template']) && empty($settings['date_archive_title'])) {
            $settings['date_archive_title'] = (string) $settings['date_archive_title_template'];
        }

        if (isset($settings['author_base'])) {
            $settings['author_base'] = $this->normalize_author_base((string) $settings['author_base']);
        }

        return $settings;
    }

    /**
     * Load settings from canonical option, with transparent compatibility fallback.
     *
     * @return array<string,mixed>
     */
    private function load_synced_settings(): array {
        $primary_raw = get_option(self::OPTION_PRIMARY, null);
        $compat_raw = get_option(self::OPTION_COMPAT, null);

        $has_primary = is_array($primary_raw);
        $has_compat = is_array($compat_raw);

        $primary = $has_primary ? $primary_raw : [];
        $compat = $has_compat ? $compat_raw : [];

        if ($has_primary) {
            if (!$has_compat || $compat !== $primary) {
                update_option(self::OPTION_COMPAT, $primary, false);
            }
            return $primary;
        }

        if ($has_compat) {
            update_option(self::OPTION_PRIMARY, $compat, false);
            return $compat;
        }

        return [];
    }

    /**
     * Persist settings to both old/new option keys for cross-UI compatibility.
     *
     * @param array<string,mixed> $settings Settings payload.
     * @return bool True when at least one option changed.
     */
    private function persist_settings(array $settings): bool {
        $saved_primary = update_option(self::OPTION_PRIMARY, $settings, false);
        $saved_compat = update_option(self::OPTION_COMPAT, $settings, false);

        wp_cache_delete(self::OPTION_PRIMARY, 'options');
        wp_cache_delete(self::OPTION_COMPAT, 'options');

        return $saved_primary || $saved_compat;
    }

    public function apply_author_base(): void {
        global $wp_rewrite;

        if (!$wp_rewrite instanceof \WP_Rewrite) {
            return;
        }

        $author_base = $this->normalize_author_base((string) $this->get_setting('author_base', 'author'));
        if ($author_base === '') {
            $author_base = 'author';
        }

        $wp_rewrite->author_base = $author_base;
        $wp_rewrite->author_structure = '/' . $author_base . '/%author%';
    }

    private function normalize_author_base(string $author_base): string {
        $normalized = trim($author_base);
        $normalized = trim($normalized, '/');
        $normalized = sanitize_title($normalized);

        return $normalized !== '' ? $normalized : 'author';
    }

    private function maybe_flush_author_rewrites(array $previous_settings, array $next_settings): void {
        $previous_base = $this->normalize_author_base((string) ($previous_settings['author_base'] ?? 'author'));
        $next_base = $this->normalize_author_base((string) ($next_settings['author_base'] ?? 'author'));

        if ($previous_base !== $next_base) {
            flush_rewrite_rules(false);
        }
    }

    /**
     * Convert UI homepage title format to template string used by legacy key.
     *
     * @param string $format
     * @return string
     */
    private function map_homepage_title_format_to_template(string $format): string {
        switch ($format) {
            case 'sitename':
                return '%sitename%';
            case 'tagline_sitename':
                return '%tagline% %sep% %sitename%';
            case 'tagline':
                return '%tagline%';
            case 'sitename_tagline':
            default:
                return '%sitename% %sep% %tagline%';
        }
    }
    
    /**
     * Get site info endpoint
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function get_site_info_endpoint(WP_REST_Request $request) {
        $page_on_front = get_option('page_on_front', 0);
        $page_for_posts = get_option('page_for_posts', 0);
        
        $site_info = [
            'show_on_front' => get_option('show_on_front', 'posts'),
            'page_on_front' => $page_on_front,
            'page_for_posts' => $page_for_posts,
            'static_page_title' => $page_on_front ? get_the_title($page_on_front) : '',
            'blog_page_title' => $page_for_posts ? get_the_title($page_for_posts) : '',
        ];
        
        return rest_ensure_response([
            'success' => true,
            'data' => $site_info,
        ]);
    }
    
    /**
     * Get bulk edit data
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function get_bulk_edit_data(WP_REST_Request $request) {
        $post_type = sanitize_key((string) ($request->get_param('post_type') ?: 'post'));
        $paged = max(1, (int) ($request->get_param('paged') ?: 1));
        $per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
        $search = sanitize_text_field((string) ($request->get_param('search') ?: ''));
        $selected_post_ids = $request->get_param('post_ids');

        if (is_string($selected_post_ids)) {
            $selected_post_ids = array_filter(array_map('intval', explode(',', $selected_post_ids)));
        } elseif (is_array($selected_post_ids)) {
            $selected_post_ids = array_filter(array_map('intval', $selected_post_ids));
        } else {
            $selected_post_ids = [];
        }
        
        $args = [
            'post_type' => $post_type,
            'posts_per_page' => $per_page,
            'paged' => $paged,
            'post_status' => 'any',
            'orderby' => 'date',
            'order' => 'DESC',
        ];
        
        if (!empty($search)) {
            $args['s'] = $search;
        }

        if (!empty($selected_post_ids)) {
            $args['post__in'] = $selected_post_ids;
            $args['orderby'] = 'post__in';
            $args['posts_per_page'] = max(count($selected_post_ids), $per_page);
            $args['paged'] = 1;
        }
        
        $query = new \WP_Query($args);
        $posts_data = [];
        
        if ($query->have_posts()) {
            while ($query->have_posts()) {
                $query->the_post();
                $post_id = get_the_ID();
                
                $posts_data[] = [
                    'id' => $post_id,
                    'title' => get_the_title(),
                    'url' => get_permalink(),
                    'excerpt' => get_the_excerpt($post_id),
                    'seo_title' => get_post_meta($post_id, '_prorank_seo_title', true),
                    'seo_description' => get_post_meta($post_id, '_prorank_seo_description', true),
                    'focus_keyword' => FocusKeywords::get_primary($post_id),
                    'focus_keywords' => FocusKeywords::get($post_id),
                ];
            }
            wp_reset_postdata();
        }
        
        return rest_ensure_response([
            'success' => true,
            'data' => [
                'posts' => $posts_data,
                'total' => $query->found_posts,
                'pages' => $query->max_num_pages,
                'current_page' => $paged,
                'selected_post_ids' => $selected_post_ids,
            ],
        ]);
    }
    
    /**
     * Save bulk edit data
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function save_bulk_edit_data(WP_REST_Request $request) {
        $updates = $request->get_param('updates');
        
        if (!is_array($updates) || empty($updates)) {
            return new WP_Error(
                'invalid_data',
                __('Invalid bulk edit data', 'prorank-seo'),
                ['status' => 400]
            );
        }
        
        $updated_count = 0;
        $errors = [];
        
        foreach ($updates as $update) {
            if (empty($update['id'])) {
                continue;
            }
            
            $post_id = absint($update['id']);
            
            // Verify post exists
            if (!get_post($post_id)) {
/* translators: %1$: value */
                $errors[] = sprintf(__('Post ID %1$d not found', 'prorank-seo'), $post_id);
                continue;
            }
            
            // Update SEO title
            if (isset($update['seo_title'])) {
                update_post_meta($post_id, '_prorank_seo_title', sanitize_text_field($update['seo_title']));
            }
            
            // Update SEO description
            if (isset($update['seo_description'])) {
                update_post_meta($post_id, '_prorank_seo_description', sanitize_textarea_field($update['seo_description']));
            }
            
            // Update focus keywords
            if (isset($update['focus_keywords'])) {
                FocusKeywords::save($post_id, $update['focus_keywords']);
            } elseif (isset($update['focus_keyword'])) {
                $kw = sanitize_text_field($update['focus_keyword']);
                if ($kw !== '') {
                    $existing  = FocusKeywords::get($post_id);
                    $secondary = array_filter($existing, function ($kw_entry) { return empty($kw_entry['primary']); });
                    $input     = array_merge([['keyword' => $kw, 'primary' => true]], array_values($secondary));
                } else {
                    $input = [];
                }
                FocusKeywords::save($post_id, $input);
            }
            
            $updated_count++;
        }
        
        return rest_ensure_response([
            'success' => true,
/* translators: %1$: value */
            'message' => sprintf(__('Updated %1$d items', 'prorank-seo'), $updated_count),
            'updated' => $updated_count,
            'errors' => $errors,
        ]);
    }
}
