<?php

/**
 * Rank Math SEO Importer
 *
 * @package ProRank\SEO\Core\Importers
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\Importers;

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


/**
 * RankMathImporter class
 *
 * Imports data from Rank Math SEO plugin
 */


class RankMathImporter extends BaseImporter {

    /**
     * Rank Math meta key mappings
     *
     * @var array
     */
    private array $meta_key_map = [
        'rank_math_title' => '_prorank_seo_title',
        'rank_math_description' => '_prorank_seo_description',
        // focus_keyword handled specially in import_single_post_meta() for multi-keyword support.
        'rank_math_canonical_url' => '_prorank_seo_canonical_url',
        // robots handled specially → _prorank_meta_robots array.
        'rank_math_facebook_title' => '_prorank_og_title',
        'rank_math_facebook_description' => '_prorank_og_description',
        'rank_math_facebook_image' => '_prorank_og_image_id',
        'rank_math_twitter_title' => '_prorank_twitter_title',
        'rank_math_twitter_description' => '_prorank_twitter_description',
        'rank_math_twitter_image' => '_prorank_twitter_image_id',
        'rank_math_primary_category' => '_prorank_seo_primary_category',
        'rank_math_breadcrumb_title' => '_prorank_seo_breadcrumb_title',
    ];

    /**
     * Check if Rank Math is available
     *
     * @return bool
     */
    public function is_available(): bool {
        return defined('RANK_MATH_VERSION') ||
               class_exists('RankMath') ||
               $this->has_rankmath_data();
    }

    /**
     * Check if Rank Math data exists in database
     *
     * @return bool
     */
    private function has_rankmath_data(): bool {
        global $wpdb;

        $result = $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->postmeta}
             WHERE meta_key LIKE 'rank_math_%'
             LIMIT 1"
        );

        return (int) $result > 0;
    }

    /**
     * Run the import
     *
     * @return array
     */
    public function import(): array {
        if (!$this->is_available()) {
            return [
                'success' => false,
                'message' => __('Rank Math SEO data not found', 'prorank-seo'),
                'stats' => $this->stats,
            ];
        }

        try {
            // Import post/page meta
            $this->import_post_meta();

            // Import term meta
            $this->import_term_meta();

            // Import settings
            $this->import_settings();

            // Import redirects
            $this->import_redirects();

            // Import schema settings
            $this->import_schema();

            // Import local SEO locations
            $this->import_locations();

            return [
                'success' => true,
                'message' => __('Rank Math SEO data imported successfully', 'prorank-seo'),
                'stats' => $this->stats,
            ];

        } catch (\Exception $e) {
            $this->log_error('Import failed: ' . $e->getMessage());

            return [
                'success' => false,
                'message' => $e->getMessage(),
                'stats' => $this->stats,
            ];
        }
    }

    /**
     * Import single post meta
     *
     * @param int $post_id Post ID
     * @return void
     */
    protected function import_single_post_meta(int $post_id): void {
        $robots_directives = [];

        foreach ($this->meta_key_map as $rm_key => $prorank_key) {
            $value = get_post_meta($post_id, $rm_key, true);

            if (!empty($value)) {
                // Rank Math stores robots in both rank_math_robots and rank_math_advanced_robots.
                if (in_array($rm_key, ['rank_math_robots', 'rank_math_advanced_robots'], true)) {
                    $robots_directives = array_merge($robots_directives, $this->extract_robots_directives($value));
                    continue;
                }

                update_post_meta($post_id, $prorank_key, $this->sanitize_meta_value(
                    $this->normalize_rankmath_post_meta_value($value, $rm_key, $post_id)
                ));
            }
        }

        $robots_directives = array_values(array_unique(array_filter($robots_directives)));
        if (!empty($robots_directives)) {
            update_post_meta($post_id, '_prorank_meta_robots', $robots_directives);
        }

        // Import focus keywords (Rank Math stores comma-separated).
        $rm_focus = (string) get_post_meta($post_id, 'rank_math_focus_keyword', true);
        if ($rm_focus !== '') {
            $parts = array_filter(array_map('trim', explode(',', $rm_focus)));
            if (!empty($parts)) {
                $kw_input = [];
                foreach ($parts as $i => $part) {
                    $kw_input[] = ['keyword' => $part, 'primary' => $i === 0];
                }
                \ProRank\SEO\Core\FocusKeywords::save($post_id, $kw_input);
            }
        }

        // Import SEO score
        $seo_score = get_post_meta($post_id, 'rank_math_seo_score', true);
        if ($seo_score) {
            update_post_meta($post_id, '_prorank_seo_score', intval($seo_score));
        }

        // Import schema data
        $schema_type = get_post_meta($post_id, 'rank_math_schema_type', true);
        if (empty($schema_type)) {
            $schema_type = get_post_meta($post_id, 'rank_math_rich_snippet', true);
        }
        if (!empty($schema_type)) {
            update_post_meta($post_id, '_prorank_seo_schema_type', $this->sanitize_meta_value($schema_type));
        }

        $schema_data = get_post_meta($post_id, 'rank_math_schema', true);
        if (!empty($schema_data)) {
            update_post_meta($post_id, '_prorank_seo_schema', $this->sanitize_meta_value($schema_data));
        }
    }

    /**
     * Import single term meta
     *
     * @param int $term_id Term ID
     * @return void
     */
    protected function import_single_term_meta(int $term_id): void {
        $term_meta_keys = [
            'rank_math_title' => '_prorank_seo_title',
            'rank_math_description' => '_prorank_seo_description',
            'rank_math_focus_keyword' => '_prorank_seo_focus_keyword',
            'rank_math_canonical_url' => '_prorank_seo_canonical',
            'rank_math_facebook_title' => '_prorank_seo_og_title',
            'rank_math_facebook_description' => '_prorank_seo_og_description',
            'rank_math_facebook_image' => '_prorank_seo_og_image',
        ];

        foreach ($term_meta_keys as $rm_key => $prorank_key) {
            $value = get_term_meta($term_id, $rm_key, true);

            if (!empty($value)) {
                update_term_meta($term_id, $prorank_key, $this->sanitize_meta_value($value));
            }
        }

        // Handle robots meta
        $robots_directives = [];
        $robots = get_term_meta($term_id, 'rank_math_robots', true);
        $robots_directives = array_merge($robots_directives, $this->extract_robots_directives($robots));
        $advanced_robots = get_term_meta($term_id, 'rank_math_advanced_robots', true);
        $robots_directives = array_merge($robots_directives, $this->extract_robots_directives($advanced_robots));
        $robots_directives = array_values(array_unique(array_filter($robots_directives)));

        if (!empty($robots_directives)) {
            update_term_meta($term_id, '_prorank_seo_robots', $robots_directives);
            if (in_array('noindex', $robots_directives, true)) {
                update_term_meta($term_id, '_prorank_seo_noindex', 'noindex');
            }
        }
    }

    /**
     * Import Rank Math settings
     *
     * @return void
     */
    protected function import_settings(): void {
        if (!in_array('settings', $this->import_types, true)) {
            return;
        }

        $site_basics = [];
        $rankmath_sources = [];

        // Import general settings
        $general = get_option('rank-math-options-general');
        if (!is_array($general) || empty($general)) {
            $general = get_option('rank_math_options_general', []);
        }
        if ($general && is_array($general)) {
            $rankmath_sources[] = $general;
            $settings = [];

            // Title separator
            if (isset($general['title_separator'])) {
                $settings['title_separator'] = $general['title_separator'];
                $site_basics['title_separator'] = $general['title_separator'];
            }

            // Breadcrumbs settings
            if (isset($general['breadcrumbs'])) {
                $settings['enable_breadcrumbs'] = $this->to_bool($general['breadcrumbs']);
            }

            if (!empty($settings)) {
                $this->merge_option_array('prorank_seo_settings', $settings);
                $this->stats['settings']++;
            }

        }

        // Import titles & meta settings
        $titles = get_option('rank-math-options-titles');
        if (!is_array($titles) || empty($titles)) {
            $titles = get_option('rank_math_options_titles', []);
        }
        if ($titles && is_array($titles)) {
            $rankmath_sources[] = $titles;
            $title_settings = [];

            if (isset($titles['homepage_title'])) {
                $title_settings['homepage_title'] = $titles['homepage_title'];
            }
            if (isset($titles['homepage_description'])) {
                $title_settings['homepage_description'] = $titles['homepage_description'];
            }

            $this->maybe_apply_static_homepage_overrides($title_settings);
            $this->map_homepage_settings_to_modern_keys($title_settings);

            if (!empty($title_settings)) {
                $this->merge_option_array('prorank_seo_titles_meta', $title_settings);
                $this->stats['settings']++;
            }

            if (!empty($titles['website_name'])) {
                $site_basics['site_name'] = sanitize_text_field((string) $titles['website_name']);
            }
            if (!empty($titles['website_alternate_name'])) {
                $site_basics['site_alternate_name'] = sanitize_text_field((string) $titles['website_alternate_name']);
            }
            if (!empty($titles['title_separator'])) {
                $site_basics['title_separator'] = sanitize_text_field((string) $titles['title_separator']);
            }
            if (!empty($titles['knowledgegraph_type'])) {
                $site_basics['site_representation'] = sanitize_text_field((string) $titles['knowledgegraph_type']);
            }
            if (!empty($titles['knowledgegraph_name'])) {
                $site_basics['organization_name'] = sanitize_text_field((string) $titles['knowledgegraph_name']);
            }
            if (!empty($titles['knowledgegraph_logo'])) {
                $site_basics['organization_logo'] = esc_url_raw((string) $titles['knowledgegraph_logo']);
            }

            $social_title_map = [
                'social_url_facebook' => 'facebook_page_url',
                'social_url_linkedin' => 'linkedin_url',
                'social_url_instagram' => 'instagram_url',
                'social_url_youtube' => 'youtube_url',
            ];
            foreach ($social_title_map as $source_key => $target_key) {
                if (!empty($titles[$source_key])) {
                    $site_basics[$target_key] = esc_url_raw((string) $titles[$source_key]);
                }
            }

            if (!empty($titles['social_url_twitter'])) {
                $site_basics['twitter_username'] = sanitize_text_field(ltrim((string) $titles['social_url_twitter'], '@'));
            } elseif (!empty($titles['twitter_author_names'])) {
                $site_basics['twitter_username'] = sanitize_text_field(ltrim((string) $titles['twitter_author_names'], '@'));
            }
        }

        // Import social settings
        $social = get_option('rank-math-options-social');
        if (!is_array($social) || empty($social)) {
            $social = get_option('rank_math_options_social', []);
        }
        if ($social && is_array($social)) {
            $rankmath_sources[] = $social;
            $social_settings = [];

            if (isset($social['facebook_url'])) {
                $social_settings['facebook_page_url'] = $social['facebook_url'];
            }
            if (isset($social['twitter_author_names'])) {
                $social_settings['twitter_username'] = $social['twitter_author_names'];
            }

            if (!empty($social_settings)) {
                $this->merge_option_array('prorank_seo_social', $social_settings);
                $this->merge_site_basics($social_settings);
                $this->stats['settings']++;
            }
        }

        // Collect additional/legacy Rank Math settings containers.
        $additional_sources = [
            get_option('rank_math_options_general', []),
            get_option('rank_math_options_titles', []),
            get_option('rank_math_options_social', []),
            get_option('rank_math_google_analytic_options', []),
        ];
        foreach ($additional_sources as $source) {
            if (is_array($source) && !empty($source)) {
                $rankmath_sources[] = $source;
            }
        }

        // Import webmaster verification codes from any known Rank Math source shape.
        $verification_codes = $this->extract_rankmath_verification_codes($rankmath_sources);
        if (!empty($verification_codes)) {
            $site_basics = array_merge($site_basics, $verification_codes);
        }

        // Import sitemap settings
        $sitemap = get_option('rank-math-options-sitemap');
        if (!is_array($sitemap) || empty($sitemap)) {
            $sitemap = get_option('rank_math_options_sitemap', []);
        }
        if ($sitemap && is_array($sitemap)) {
            $sitemap_settings = [];

            if (array_key_exists('sitemap_general', $sitemap)) {
                $sitemap_settings['enable_xml_sitemap'] = $this->to_bool($sitemap['sitemap_general']);
            } elseif (array_key_exists('enable_xml_sitemap', $sitemap)) {
                $sitemap_settings['enable_xml_sitemap'] = $this->to_bool($sitemap['enable_xml_sitemap']);
            }

            if (array_key_exists('include_images', $sitemap)) {
                $sitemap_settings['include_images'] = $this->to_bool($sitemap['include_images']);
            }

            if (isset($sitemap['links_per_sitemap']) && is_numeric($sitemap['links_per_sitemap'])) {
                $sitemap_settings['posts_per_sitemap'] = max(1, (int) $sitemap['links_per_sitemap']);
            }

            $enabled_post_types = $this->extract_rankmath_sitemap_post_types($sitemap);
            if (!empty($enabled_post_types)) {
                $sitemap_settings['include_post_types'] = $enabled_post_types;
            }

            $enabled_taxonomies = $this->extract_rankmath_sitemap_taxonomies($sitemap);
            if (!empty($enabled_taxonomies)) {
                $sitemap_settings['include_taxonomies'] = $enabled_taxonomies;
            }

            if (!empty($sitemap_settings)) {
                $this->merge_option_array('prorank_seo_sitemap', $sitemap_settings);
                $this->stats['settings']++;
            }
        }

        $this->merge_site_basics($site_basics);
    }

    /**
     * Extract enabled post types from Rank Math sitemap settings.
     *
     * @param array<string,mixed> $sitemap
     * @return string[]
     */
    private function extract_rankmath_sitemap_post_types(array $sitemap): array {
        $post_types = [];

        foreach ($sitemap as $key => $value) {
            if (!preg_match('/^pt_(.+?)_sitemap$/', (string) $key, $matches)) {
                continue;
            }

            if (!$this->to_bool($value)) {
                continue;
            }

            $post_types[] = sanitize_key((string) $matches[1]);
        }

        $post_types = array_values(array_unique(array_filter($post_types)));
        sort($post_types);

        return $post_types;
    }

    /**
     * Extract enabled taxonomies from Rank Math sitemap settings.
     *
     * @param array<string,mixed> $sitemap
     * @return string[]
     */
    private function extract_rankmath_sitemap_taxonomies(array $sitemap): array {
        $taxonomies = [];

        foreach ($sitemap as $key => $value) {
            if (!preg_match('/^tax_(.+?)_sitemap$/', (string) $key, $matches)) {
                continue;
            }

            if (!$this->to_bool($value)) {
                continue;
            }

            $taxonomies[] = sanitize_key((string) $matches[1]);
        }

        $taxonomies = array_values(array_unique(array_filter($taxonomies)));
        sort($taxonomies);

        return $taxonomies;
    }

    /**
     * Prefer static front-page SEO meta when Rank Math global homepage values are
     * empty or default templates.
     *
     * Rank Math often stores real homepage SEO on the static front page itself
     * (rank_math_title/rank_math_description), while global homepage settings may
     * remain generic placeholders.
     *
     * @param array<string,mixed> $title_settings Title settings payload.
     * @return void
     */
    private function maybe_apply_static_homepage_overrides(array &$title_settings): void {
        if (get_option('show_on_front', 'posts') !== 'page') {
            return;
        }

        $front_page_id = (int) get_option('page_on_front', 0);
        if ($front_page_id <= 0) {
            return;
        }

        $front_title = (string) get_post_meta($front_page_id, 'rank_math_title', true);
        $front_description = (string) get_post_meta($front_page_id, 'rank_math_description', true);

        $current_homepage_title = isset($title_settings['homepage_title']) ? (string) $title_settings['homepage_title'] : '';
        $current_homepage_description = isset($title_settings['homepage_description']) ? (string) $title_settings['homepage_description'] : '';

        $default_title_templates = [
            '%sitename% %sep% %tagline%',
            '%sitename% %page% %sep% %sitedesc%',
            '%sitename% %sep% %sitedesc%',
        ];

        if (
            $front_title !== ''
            && ($current_homepage_title === '' || in_array(trim($current_homepage_title), $default_title_templates, true))
        ) {
            $title_settings['homepage_title'] = $front_title;
        }

        if ($front_description !== '' && trim($current_homepage_description) === '') {
            $title_settings['homepage_description'] = $front_description;
        }
    }

    /**
     * Mirror legacy Rank Math homepage fields into ProRank's modern homepage keys.
     *
     * @param array<string,mixed> $title_settings
     * @return void
     */
    private function map_homepage_settings_to_modern_keys(array &$title_settings): void {
        $legacy_title = isset($title_settings['homepage_title']) ? trim((string) $title_settings['homepage_title']) : '';
        $legacy_meta = isset($title_settings['homepage_description']) ? trim((string) $title_settings['homepage_description']) : '';
        $is_static_front = get_option('show_on_front', 'posts') === 'page';

        if ($legacy_title !== '') {
            if ($is_static_front) {
                $title_settings['static_homepage_title_type'] = 'custom';
                $title_settings['static_homepage_custom_title'] = $legacy_title;
            } else {
                $title_settings['homepage_title_type'] = 'custom';
                $title_settings['homepage_custom_title'] = $legacy_title;
            }

            // Keep older alias for backward compatibility.
            $title_settings['custom_homepage_title'] = $legacy_title;
        }

        if ($legacy_meta !== '') {
            if ($is_static_front) {
                $title_settings['static_homepage_meta_type'] = 'custom';
                $title_settings['static_homepage_custom_meta'] = $legacy_meta;
            } else {
                $title_settings['homepage_meta_type'] = 'custom';
                $title_settings['homepage_custom_meta'] = $legacy_meta;
            }
        }
    }

    /**
     * Import Rank Math redirects
     *
     * @return void
     */
    protected function import_redirects(): void {
        if (!in_array('redirects', $this->import_types, true)) {
            return;
        }

        global $wpdb;

        // Rank Math stores redirects in rank_math_redirections table
        $table = $wpdb->prefix . 'rank_math_redirections';
        if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) !== $table) {
            return;
        }

        $redirects = $wpdb->get_results(
            "SELECT * FROM {$table} WHERE status = 'active'"
        );

        foreach ($redirects as $redirect) {
            $sources = $this->extract_rankmath_sources($redirect->sources ?? null);
            if (empty($sources)) {
                continue;
            }

            foreach ($sources as $source) {
                $prorank_redirect = [
                    'from' => sanitize_text_field($source['pattern']),
                    'to' => isset($redirect->url_to) ? (string) $redirect->url_to : '',
                    'type' => isset($redirect->header_code) ? intval($redirect->header_code) : 301,
                    'is_regex' => !empty($source['is_regex']),
                    'status' => 'active',
                    'notes' => 'Imported from Rank Math',
                ];

                if ($this->save_redirect($prorank_redirect)) {
                    $this->stats['redirects']++;
                }
            }
        }
    }

    /**
     * Import schema settings
     *
     * @return void
     */
    protected function import_schema(): void {
        if (!in_array('schema', $this->import_types, true)) {
            return;
        }

        $schema_options = get_option('rank-math-options-titles');
        if (!is_array($schema_options) || empty($schema_options)) {
            $schema_options = get_option('rank_math_options_titles', []);
        }

        if ($schema_options && is_array($schema_options)) {
            $prorank_schema = [];

            if (isset($schema_options['knowledgegraph_type'])) {
                $prorank_schema['site_representation'] = $schema_options['knowledgegraph_type'];
            }

            if (isset($schema_options['knowledgegraph_name'])) {
                $prorank_schema['organization_name'] = $schema_options['knowledgegraph_name'];
            }

            if (isset($schema_options['knowledgegraph_logo'])) {
                $prorank_schema['organization_logo'] = $schema_options['knowledgegraph_logo'];
            }

            if (!empty($prorank_schema)) {
                $this->merge_option_array('prorank_seo_schema', $prorank_schema);
                $this->merge_site_basics($prorank_schema);
                $this->stats['settings']++;
            }
        }
    }

    /**
     * Import Rank Math local SEO locations.
     *
     * @return void
     */
    protected function import_locations(): void {
        if (!in_array('locations', $this->import_types, true)) {
            return;
        }

        $imported = 0;

        $settings = $this->get_rankmath_local_settings();
        if (!empty($settings)) {
            $from_settings = $this->build_location_from_rankmath_settings($settings);
            if (!empty($from_settings) && $this->save_location($from_settings)) {
                $imported++;
            }
        }

        $location_posts = $this->get_rankmath_location_posts();
        foreach ($location_posts as $location_post) {
            $location = $this->build_location_from_rankmath_post((int) $location_post->ID);
            if (!empty($location) && $this->save_location($location)) {
                $imported++;
            }
        }

        // Fallback: many Rank Math setups store LocalBusiness data directly in
        // post meta (rank_math_schema_LocalBusiness) without using a locations CPT.
        $schema_locations = $this->get_rankmath_schema_locations();
        foreach ($schema_locations as $location) {
            if (!empty($location) && $this->save_location($location)) {
                $imported++;
            }
        }

        $this->stats['locations'] += $imported;
    }

    /**
     * Map meta key
     *
     * @param string $competitor_key Rank Math meta key
     * @return string ProRank meta key
     */
    protected function map_meta_key(string $competitor_key): string {
        return $this->meta_key_map[$competitor_key] ?? $competitor_key;
    }

    /**
     * Normalize Rank Math post meta before storing it under ProRank keys.
     *
     * Rank Math can store per-post titles as templates such as
     * "%title% %sep% %sitename%". Resolve supported title variables during
     * migration so raw competitor placeholders cannot leak into output.
     *
     * @param mixed  $value   Rank Math meta value.
     * @param string $rm_key  Rank Math meta key.
     * @param int    $post_id Post ID.
     * @return mixed
     */
    private function normalize_rankmath_post_meta_value($value, string $rm_key, int $post_id) {
        if (!is_string($value) || strpos($value, '%') === false) {
            return $value;
        }

        $title_keys = [
            'rank_math_title',
            'rank_math_facebook_title',
            'rank_math_twitter_title',
        ];

        if (!in_array($rm_key, $title_keys, true)) {
            return $value;
        }

        $post = get_post($post_id);
        if (!$post instanceof \WP_Post || !class_exists('\ProRank\SEO\Core\DynamicVariables')) {
            return $value;
        }

        $template = str_replace(
            ['%%sitedesc%%', '%sitedesc%', '%%page%%', '%page%'],
            ['%%tagline%%', '%tagline%', '', ''],
            $value
        );

        $context_type = $post->post_type === 'page' ? 'page' : 'post';
        if ($post->post_type === 'product') {
            $context_type = 'product';
        }

        $parser = new \ProRank\SEO\Core\DynamicVariables();
        $resolved = $parser->parse($template, $post, $context_type);
        $resolved = trim(html_entity_decode(wp_strip_all_tags($resolved), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        $resolved = (string) preg_replace('/\s+/', ' ', $resolved);

        return $resolved !== '' ? $resolved : $value;
    }

    /**
     * Save redirect
     *
     * @param array $redirect Redirect data
     * @return bool
     */
    protected function save_redirect(array $redirect): bool {
        return parent::save_redirect($redirect);
    }

    /**
     * Extract robots directives from Rank Math value.
     *
     * @param mixed $value Stored robots value.
     * @return array
     */
    private function extract_robots_directives($value): array {
        if (is_string($value)) {
            $decoded = maybe_unserialize($value);
            if (is_array($decoded)) {
                $value = $decoded;
            } else {
                $value = preg_split('/[\s,]+/', strtolower($value)) ?: [];
            }
        }

        if (!is_array($value)) {
            return [];
        }

        $directives = [];
        foreach ($value as $directive) {
            $directive = sanitize_key((string) $directive);
            if ($directive !== '') {
                $directives[] = $directive;
            }
        }

        return $directives;
    }

    /**
     * Extract all source patterns from Rank Math redirect source column.
     *
     * @param mixed $raw_sources Serialized/json/array value.
     * @return array<int, array{pattern:string,is_regex:bool}>
     */
    private function extract_rankmath_sources($raw_sources): array {
        $sources = maybe_unserialize($raw_sources);

        if (is_string($sources)) {
            $decoded = json_decode($sources, true);
            if (is_array($decoded)) {
                $sources = $decoded;
            }
        }

        if (!is_array($sources)) {
            return [];
        }

        $parsed = [];
        foreach ($sources as $source) {
            if (!is_array($source)) {
                continue;
            }

            $pattern = isset($source['pattern'])
                ? trim((string) $source['pattern'])
                : '';
            if ($pattern === '') {
                continue;
            }

            $comparison = isset($source['comparison']) ? sanitize_key((string) $source['comparison']) : '';
            $is_regex = !empty($source['is_regex']) || in_array($comparison, ['regex', 'regexp'], true);

            // Rank Math's "contains" comparison does substring matching.
            // ProRank's redirect engine only exact-matches non-regex patterns,
            // so convert "contains" patterns to delimited regex equivalents.
            // Note: regex redirects only execute in Premium; in Free they are
            // stored but skipped at runtime.
            if ($comparison === 'contains' && !$is_regex) {
                $pattern = '/.*' . preg_quote($pattern, '/') . '.*/';
                $is_regex = true;
            }

            $parsed[] = [
                'pattern' => $pattern,
                'is_regex' => $is_regex,
            ];
        }

        return $parsed;
    }

    /**
     * Normalize truthy/falsy values from competitor options.
     *
     * @param mixed $value Raw value.
     * @return bool
     */
    private function to_bool($value): bool {
        if (is_bool($value)) {
            return $value;
        }

        if (is_numeric($value)) {
            return (int) $value === 1;
        }

        $value = strtolower((string) $value);
        return in_array($value, ['1', 'yes', 'on', 'true', 'enabled'], true);
    }

    /**
     * Fetch Rank Math local SEO settings option.
     *
     * @return array
     */
    private function get_rankmath_local_settings(): array {
        $option_names = [
            'rank-math-options-local-seo',
            'rank_math_options_local_seo',
            'rank-math-options-local',
        ];

        foreach ($option_names as $option_name) {
            $raw = get_option($option_name);
            if (!$raw) {
                continue;
            }

            if (is_string($raw)) {
                $decoded = json_decode($raw, true);
                if (is_array($decoded)) {
                    $raw = $decoded;
                } else {
                    $raw = maybe_unserialize($raw);
                }
            }

            if (is_array($raw) && !empty($raw)) {
                return $raw;
            }
        }

        return [];
    }

    /**
     * Build a location record from Rank Math settings.
     *
     * @param array $settings Rank Math local SEO settings.
     * @return array
     */
    private function build_location_from_rankmath_settings(array $settings): array {
        $name = $this->extract_nested_value($settings, [
            'local_seo_name',
            'business_name',
            'company_name',
            'knowledgegraph_name',
            'person_name',
        ]);

        $address = $this->extract_nested_value($settings, [
            'address',
            'street_address',
            'address_street',
        ]);

        $city = $this->extract_nested_value($settings, ['locality', 'city', 'address_locality']);
        $state = $this->extract_nested_value($settings, ['region', 'state', 'address_region']);
        $postal = $this->extract_nested_value($settings, ['postal_code', 'postcode', 'zip']);
        $country = $this->extract_nested_value($settings, ['country', 'address_country']);

        if (empty($name) && empty($address) && empty($city)) {
            return [];
        }

        return [
            'name' => (string) ($name ?: get_bloginfo('name')),
            'description' => (string) ($this->extract_nested_value($settings, ['description', 'local_seo_description']) ?? ''),
            'address' => (string) ($address ?? ''),
            'city' => (string) ($city ?? ''),
            'state' => (string) ($state ?? ''),
            'postalCode' => (string) ($postal ?? ''),
            'country' => (string) ($country ?? ''),
            'phone' => (string) ($this->extract_nested_value($settings, ['phone', 'telephone']) ?? ''),
            'email' => (string) ($this->extract_nested_value($settings, ['email']) ?? ''),
            'latitude' => (float) ($this->extract_nested_value($settings, ['geo_lat', 'latitude', 'lat']) ?? 0),
            'longitude' => (float) ($this->extract_nested_value($settings, ['geo_lng', 'longitude', 'lng']) ?? 0),
            'openingHours' => $this->normalize_rankmath_opening_hours($this->extract_nested_value($settings, ['opening_hours', 'openingHours'])),
            'status' => 'active',
            'isPrimary' => true,
        ];
    }

    /**
     * Get location posts created by Rank Math Local SEO.
     *
     * @return array
     */
    private function get_rankmath_location_posts(): array {
        $post_types = ['rank_math_location', 'rank-math-location'];
        $found = [];

        foreach ($post_types as $post_type) {
            if (!post_type_exists($post_type)) {
                continue;
            }

            $posts = get_posts([
                'post_type' => $post_type,
                'posts_per_page' => -1,
                'post_status' => ['publish', 'private', 'draft', 'pending'],
            ]);

            if (!empty($posts)) {
                $found = array_merge($found, $posts);
            }
        }

        return $found;
    }

    /**
     * Get LocalBusiness entries from Rank Math schema post meta.
     *
     * @return array<int,array>
     */
    private function get_rankmath_schema_locations(): array {
        global $wpdb;

        $rows = $wpdb->get_results(
            "SELECT post_id, meta_key, meta_value
             FROM {$wpdb->postmeta}
             WHERE meta_key IN ('rank_math_schema_LocalBusiness', 'rank_math_schema')"
        );

        if (empty($rows)) {
            return [];
        }

        $locations = [];
        foreach ($rows as $row) {
            $post_id = isset($row->post_id) ? (int) $row->post_id : 0;
            $meta_key = isset($row->meta_key) ? (string) $row->meta_key : '';
            if ($post_id <= 0 || $meta_key === '') {
                continue;
            }

            $schemas = $this->extract_localbusiness_schemas_from_meta($meta_key, $row->meta_value);
            foreach ($schemas as $schema) {
                $location = $this->build_location_from_rankmath_schema($post_id, $schema);
                if (!empty($location)) {
                    $locations[] = $location;
                }
            }
        }

        return $locations;
    }

    /**
     * Extract LocalBusiness schema payload(s) from Rank Math meta values.
     *
     * @param string $meta_key Rank Math meta key.
     * @param mixed  $meta_value Stored meta value.
     * @return array<int,array>
     */
    private function extract_localbusiness_schemas_from_meta(string $meta_key, $meta_value): array {
        $payload = $meta_value;
        if (is_string($payload)) {
            $decoded = json_decode($payload, true);
            if (is_array($decoded)) {
                $payload = $decoded;
            } else {
                $payload = maybe_unserialize($payload);
            }
        }

        if (!is_array($payload)) {
            return [];
        }

        if ($meta_key === 'rank_math_schema_LocalBusiness') {
            return [$payload];
        }

        // rank_math_schema may contain a keyed collection of schema nodes.
        $schemas = [];
        foreach ($payload as $value) {
            if (!is_array($value)) {
                continue;
            }

            $type = strtolower((string) ($value['@type'] ?? $value['type'] ?? ''));
            if ($type === 'localbusiness' || str_contains($type, 'localbusiness')) {
                $schemas[] = $value;
            }
        }

        return $schemas;
    }

    /**
     * Build location payload from Rank Math LocalBusiness schema node.
     *
     * @param int   $post_id Post ID that owns schema.
     * @param array $schema  Schema payload.
     * @return array
     */
    private function build_location_from_rankmath_schema(int $post_id, array $schema): array {
        $address = isset($schema['address']) && is_array($schema['address']) ? $schema['address'] : [];
        $geo = isset($schema['geo']) && is_array($schema['geo']) ? $schema['geo'] : [];
        $metadata = isset($schema['metadata']) && is_array($schema['metadata']) ? $schema['metadata'] : [];

        $name = $this->cleanup_rankmath_template_value((string) ($schema['name'] ?? ''));
        if ($name === '') {
            $name = (string) get_the_title($post_id);
        }

        $street = (string) ($address['streetAddress'] ?? $address['street'] ?? '');
        $city = (string) ($address['addressLocality'] ?? $address['locality'] ?? '');
        $state = (string) ($address['addressRegion'] ?? $address['region'] ?? '');
        $postal = (string) ($address['postalCode'] ?? $address['postcode'] ?? '');
        $country = (string) ($address['addressCountry'] ?? $address['country'] ?? '');

        if ($name === '' && $street === '' && $city === '') {
            return [];
        }

        $image_url = '';
        if (isset($schema['image']) && is_array($schema['image'])) {
            $image_url = (string) ($schema['image']['url'] ?? '');
        } elseif (is_string($schema['image'] ?? null)) {
            $image_url = (string) $schema['image'];
        }

        $latitude = (float) ($geo['latitude'] ?? $geo['lat'] ?? 0);
        $longitude = (float) ($geo['longitude'] ?? $geo['lng'] ?? 0);

        $status = get_post_status($post_id);
        return [
            'id' => 'loc_rankmath_schema_' . $post_id,
            'name' => $name,
            'description' => (string) ($schema['description'] ?? wp_strip_all_tags((string) get_post_field('post_excerpt', $post_id))),
            'address' => $street,
            'city' => $city,
            'state' => $state,
            'postalCode' => $postal,
            'country' => $country,
            'phone' => (string) ($schema['telephone'] ?? $schema['phone'] ?? ''),
            'email' => (string) ($schema['email'] ?? ''),
            'latitude' => $latitude,
            'longitude' => $longitude,
            'openingHours' => $this->normalize_rankmath_opening_hours(
                $schema['openingHoursSpecification'] ?? $schema['openingHours'] ?? []
            ),
            'status' => ($status === 'publish') ? 'active' : 'inactive',
            'isPrimary' => !empty($metadata['isPrimary']),
            'images' => $image_url !== '' ? [$image_url] : [],
        ];
    }

    /**
     * Convert Rank Math template placeholders to empty values for import.
     *
     * @param string $value Raw value.
     * @return string
     */
    private function cleanup_rankmath_template_value(string $value): string {
        $value = trim($value);
        if ($value === '') {
            return '';
        }

        if (preg_match('/^%[^%]+%$/', $value)) {
            return '';
        }

        return $value;
    }

    /**
     * Build location payload from a Rank Math location post.
     *
     * @param int $post_id Location post id.
     * @return array
     */
    private function build_location_from_rankmath_post(int $post_id): array {
        $name = get_the_title($post_id);
        $address = $this->get_meta_any($post_id, [
            'rank_math_address',
            'rank_math_street_address',
            '_rank_math_address',
            '_rank_math_street_address',
        ]);

        $city = $this->get_meta_any($post_id, [
            'rank_math_locality',
            'rank_math_city',
            '_rank_math_locality',
            '_rank_math_city',
        ]);

        if (empty($name) && empty($address) && empty($city)) {
            return [];
        }

        $status = get_post_status($post_id);

        return [
            'id' => 'loc_rankmath_' . $post_id,
            'name' => $name,
            'description' => wp_strip_all_tags((string) get_post_field('post_content', $post_id)),
            'address' => (string) ($address ?? ''),
            'city' => (string) ($city ?? ''),
            'state' => (string) ($this->get_meta_any($post_id, ['rank_math_region', 'rank_math_state', '_rank_math_region']) ?? ''),
            'postalCode' => (string) ($this->get_meta_any($post_id, ['rank_math_postal_code', 'rank_math_zip', '_rank_math_postal_code']) ?? ''),
            'country' => (string) ($this->get_meta_any($post_id, ['rank_math_country', '_rank_math_country']) ?? ''),
            'phone' => (string) ($this->get_meta_any($post_id, ['rank_math_phone', '_rank_math_phone']) ?? ''),
            'email' => (string) ($this->get_meta_any($post_id, ['rank_math_email', '_rank_math_email']) ?? ''),
            'latitude' => (float) ($this->get_meta_any($post_id, ['rank_math_latitude', 'rank_math_lat', '_rank_math_latitude']) ?? 0),
            'longitude' => (float) ($this->get_meta_any($post_id, ['rank_math_longitude', 'rank_math_lng', '_rank_math_longitude']) ?? 0),
            'openingHours' => $this->normalize_rankmath_opening_hours($this->get_meta_any($post_id, ['rank_math_opening_hours', '_rank_math_opening_hours'])),
            'status' => ($status === 'publish') ? 'active' : 'inactive',
            'isPrimary' => false,
        ];
    }

    /**
     * Read a post meta value from a list of possible keys.
     *
     * @param int   $post_id Post id.
     * @param array $keys    Candidate keys.
     * @return mixed|null
     */
    private function get_meta_any(int $post_id, array $keys) {
        foreach ($keys as $key) {
            $value = get_post_meta($post_id, (string) $key, true);
            if ($value !== '' && $value !== null) {
                return $value;
            }
        }

        return null;
    }

    /**
     * Extract first non-empty value from a nested settings array.
     *
     * @param array $data Settings array.
     * @param array $keys Candidate keys.
     * @param int   $depth Internal recursion depth.
     * @return mixed|null
     */
    private function extract_nested_value(array $data, array $keys, int $depth = 0) {
        if ($depth > 5) {
            return null;
        }

        foreach ($keys as $key) {
            if (array_key_exists($key, $data) && $data[$key] !== '' && $data[$key] !== null) {
                return $data[$key];
            }
        }

        foreach ($data as $value) {
            if (!is_array($value)) {
                continue;
            }

            $nested = $this->extract_nested_value($value, $keys, $depth + 1);
            if ($nested !== null && $nested !== '') {
                return $nested;
            }
        }

        return null;
    }

    /**
     * Normalize Rank Math opening hours data.
     *
     * @param mixed $hours Raw opening hours payload.
     * @return array
     */
    private function normalize_rankmath_opening_hours($hours): array {
        if (is_string($hours)) {
            $decoded = json_decode($hours, true);
            if (is_array($decoded)) {
                $hours = $decoded;
            } else {
                $hours = maybe_unserialize($hours);
            }
        }

        if (!is_array($hours)) {
            return [];
        }

        return $this->normalize_opening_hours($hours);
    }

    /**
     * Extract verification codes from Rank Math option payloads.
     *
     * @param array<int, array> $sources Option arrays to scan.
     * @return array<string,string>
     */
    private function extract_rankmath_verification_codes(array $sources): array {
        $result = [];

        $field_map = [
            'google_verification' => ['google_verify', 'google_verification', 'google_site_verification', 'google-site-verification'],
            'bing_verification' => ['bing_verify', 'bing_verification', 'msverify', 'ms_validate', 'msvalidate.01'],
            'baidu_verification' => ['baidu_verify', 'baidu_verification', 'baidu-site-verification'],
            'yandex_verification' => ['yandex_verify', 'yandex_verification', 'yandexverify', 'yandex-verification'],
            'pinterest_verification' => ['pinterest_verify', 'pinterest_verification', 'p:domain_verify', 'domain_verify'],
            'norton_verification' => ['norton_verify', 'norton_verification', 'norton-safeweb-site-verification'],
        ];

        foreach ($field_map as $target_key => $candidate_keys) {
            foreach ($sources as $source) {
                $value = $this->find_first_matching_value($source, $candidate_keys);
                if ($value === null) {
                    continue;
                }
                $clean = $this->sanitize_verification_code($value);
                if ($clean !== '') {
                    $result[$target_key] = $clean;
                    break;
                }
            }
        }

        return $result;
    }

    /**
     * Find a matching value in nested arrays by flexible key matching.
     *
     * @param mixed $payload Source payload.
     * @param array<int,string> $candidate_keys Candidate keys.
     * @return string|null
     */
    private function find_first_matching_value($payload, array $candidate_keys): ?string {
        if (!is_array($payload)) {
            return null;
        }

        $normalized_candidates = array_map(
            fn(string $key): string => $this->normalize_setting_key($key),
            $candidate_keys
        );

        foreach ($payload as $key => $value) {
            $normalized_key = $this->normalize_setting_key((string) $key);
            if (in_array($normalized_key, $normalized_candidates, true)) {
                if (is_scalar($value) && trim((string) $value) !== '') {
                    return (string) $value;
                }
            }

            if (is_array($value)) {
                $nested = $this->find_first_matching_value($value, $candidate_keys);
                if ($nested !== null && trim($nested) !== '') {
                    return $nested;
                }
            }
        }

        return null;
    }

    /**
     * Normalize setting keys for tolerant matching.
     *
     * @param string $key Raw key.
     * @return string
     */
    private function normalize_setting_key(string $key): string {
        $key = strtolower(trim($key));
        return str_replace(['-', '.', ':', ' '], '_', $key);
    }

    /**
     * Normalize verification values. Accepts raw codes or full meta tags.
     *
     * @param string $raw Raw option value.
     * @return string
     */
    private function sanitize_verification_code(string $raw): string {
        $raw = trim($raw);
        if ($raw === '') {
            return '';
        }

        if (stripos($raw, '<meta') !== false) {
            if (preg_match('/content\\s*=\\s*["\\\']([^"\\\']+)["\\\']/i', $raw, $m)) {
                $raw = $m[1];
            }
        }

        return sanitize_text_field($raw);
    }
}
