<?php
/**
 * DBA GeoBot Internal Linker — Prevents orphan posts & boosts Google indexing
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  Google will NOT index orphan posts. Every city post MUST be:
 *    1. Linked FROM a hub/pillar page (country-level index)
 *    2. Linked TO 3-5 related city posts (same region first)
 *    3. Linked FROM related city posts (retroactive back-links)
 *    4. Part of a breadcrumb trail (Country > Region > City)
 *    5. Tagged with proper schema (BreadcrumbList JSON-LD)
 *
 *  This class handles ALL of these automatically, per country.
 * ═══════════════════════════════════════════════════════════════════════
 *
 * @since 2.8.0
 */

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

class DBA_GeoBot_Linker {

    private static $instance = null;

    /** Number of related city links to inject per post */
    const RELATED_CITIES_COUNT = 5;

    /** Option key suffix for hub page ID */
    const HUB_PAGE_OPTION_SUFFIX = '_hub_page_id';

    public static function get_instance() {
        if ( null === self::$instance ) self::$instance = new self();
        return self::$instance;
    }

    private function __construct() {
        // Inject Related Cities block into single post display
        add_filter( 'the_content', array( $this, 'inject_related_cities_block' ), 20 );

        // Inject JSON-LD breadcrumb schema
        add_action( 'wp_head', array( $this, 'inject_breadcrumb_schema' ), 5 );

        // AJAX: Retroactive linking
        add_action( 'wp_ajax_dba_geo_retroactive_link', array( $this, 'ajax_retroactive_link' ) );

        // AJAX: Create/update hub page
        add_action( 'wp_ajax_dba_geo_update_hub', array( $this, 'ajax_update_hub_page' ) );
    }

    /* =================================================================
     *  RELATED CITIES BLOCK — Injected at bottom of every GeoBot post
     * ================================================================= */

    /**
     * Get all published GeoBot posts for a country, indexed by commune.
     *
     * @param string $country_key
     * @return array [ 'commune_name' => [ 'post_id' => int, 'title' => string, 'permalink' => string, 'region' => string, 'departement' => string ] ]
     */
    public function get_country_posts( string $country_key ) {
        $config = DBA_GeoBot_Registry::get_country( $country_key );
        if ( ! $config ) return array();

        $meta_key = $config['meta_prefix'] . 'auto_created';

        $posts = get_posts( array(
            'post_type'      => 'post',
            'post_status'    => 'publish',
            'posts_per_page' => 500,
            'meta_query'     => array(
                array( 'key' => $meta_key, 'compare' => 'EXISTS' ),
            ),
            'fields' => 'ids',
        ) );

        $index = array();
        foreach ( $posts as $pid ) {
            $commune = get_post_meta( $pid, $config['meta_prefix'] . 'commune', true );
            $region  = get_post_meta( $pid, $config['meta_prefix'] . 'region', true );
            if ( ! $commune ) continue;

            $index[ $commune ] = array(
                'post_id'     => $pid,
                'title'       => get_the_title( $pid ),
                'permalink'   => get_permalink( $pid ),
                'region'      => $region,
                'commune'     => $commune,
            );
        }

        return $index;
    }

    /**
     * Find related city posts for a given commune.
     * Priority: same region > same country > random.
     *
     * @param string $country_key
     * @param string $current_commune
     * @param string $current_region
     * @param int    $count  Number of related cities to return.
     * @return array Array of [ 'title' => ..., 'permalink' => ..., 'commune' => ..., 'region' => ... ]
     */
    public function find_related_cities( string $country_key, string $current_commune, string $current_region, int $count = 5 ) {
        $all_posts = $this->get_country_posts( $country_key );

        // Remove current commune
        unset( $all_posts[ $current_commune ] );

        if ( empty( $all_posts ) ) return array();

        // Separate: same region vs different region
        $same_region = array();
        $diff_region = array();

        foreach ( $all_posts as $commune => $data ) {
            if ( strcasecmp( $data['region'], $current_region ) === 0 ) {
                $same_region[ $commune ] = $data;
            } else {
                $diff_region[ $commune ] = $data;
            }
        }

        // Shuffle both pools for variety
        $same_keys = array_keys( $same_region );
        $diff_keys = array_keys( $diff_region );
        shuffle( $same_keys );
        shuffle( $diff_keys );

        // Pick: prioritize same region, fill with others
        $result = array();

        foreach ( $same_keys as $key ) {
            if ( count( $result ) >= $count ) break;
            $result[] = $same_region[ $key ];
        }

        foreach ( $diff_keys as $key ) {
            if ( count( $result ) >= $count ) break;
            $result[] = $diff_region[ $key ];
        }

        return $result;
    }

    /**
     * Build the "Related Investment Opportunities" HTML block.
     *
     * @param string $country_key
     * @param string $current_commune
     * @param string $current_region
     * @return string HTML block (or empty string if no related posts).
     */
    public function build_related_cities_html( string $country_key, string $current_commune, string $current_region ) {
        $config = DBA_GeoBot_Registry::get_country( $country_key );
        if ( ! $config ) return '';

        $related = $this->find_related_cities( $country_key, $current_commune, $current_region, self::RELATED_CITIES_COUNT );

        if ( empty( $related ) ) return '';

        // Localized headings
        $headings = array(
            'fr' => 'Autres opportunités d\'investissement en France',
            'it' => 'Altre opportunità di investimento in Italia',
            'es' => 'Otras oportunidades de inversión en España',
            'pt' => 'Outras oportunidades de investimento em Portugal',
        );
        $lang = $config['language'] ?? 'fr';
        $heading = $headings[ $lang ] ?? $headings['fr'];

        // Hub page link
        $hub_page_id = $this->get_hub_page_id( $country_key );
        $hub_link = $hub_page_id ? get_permalink( $hub_page_id ) : '';

        // Localized "See all cities" text
        $see_all_text = array(
            'fr' => 'Voir toutes nos analyses par ville',
            'it' => 'Vedi tutte le nostre analisi per città',
            'es' => 'Ver todos nuestros análisis por ciudad',
            'pt' => 'Ver todas as nossas análises por cidade',
        );

        $html  = "\n\n<!-- GeoBot Internal Linking Block -->\n";
        $html .= '<div class="dba-geo-related-cities" style="margin-top:35px;padding:25px 30px;background:#f8f9fa;border-left:4px solid #1565c0;border-radius:0 6px 6px 0;">' . "\n";
        $html .= '<h3 style="margin:0 0 15px;font-size:18px;color:#1565c0;">' . esc_html( $heading ) . '</h3>' . "\n";
        $html .= '<ul style="list-style:none;padding:0;margin:0;">' . "\n";

        foreach ( $related as $city ) {
            $html .= '<li style="margin-bottom:8px;padding-left:0;">';
            $html .= '<a href="' . esc_url( $city['permalink'] ) . '" style="text-decoration:none;color:#1565c0;font-weight:500;">';
            $html .= '→ ' . esc_html( $city['title'] );
            $html .= '</a>';
            if ( ! empty( $city['region'] ) && strcasecmp( $city['region'], $current_region ) !== 0 ) {
                $html .= ' <span style="color:#999;font-size:13px;">(' . esc_html( $city['region'] ) . ')</span>';
            }
            $html .= '</li>' . "\n";
        }

        $html .= '</ul>' . "\n";

        // Link to hub page
        if ( $hub_link ) {
            $html .= '<p style="margin:15px 0 0;"><a href="' . esc_url( $hub_link ) . '" style="color:#1565c0;font-weight:600;">';
            $html .= esc_html( $see_all_text[ $lang ] ?? $see_all_text['fr'] );
            $html .= ' →</a></p>' . "\n";
        }

        $html .= '</div>' . "\n";
        $html .= "<!-- /GeoBot Internal Linking Block -->\n";

        return $html;
    }

    /**
     * Filter: Auto-inject related cities block into GeoBot posts on display.
     * Only injects if block is not already in saved post_content.
     */
    public function inject_related_cities_block( $content ) {
        if ( ! is_singular( 'post' ) ) return $content;

        // Skip if already has static block in content
        if ( strpos( $content, '<!-- GeoBot Internal Linking Block -->' ) !== false ) {
            return $content;
        }

        $post_id = get_the_ID();
        if ( ! $post_id ) return $content;

        // Detect which country this post belongs to
        $country_key = $this->detect_post_country( $post_id );
        if ( ! $country_key ) return $content;

        $config  = DBA_GeoBot_Registry::get_country( $country_key );
        $commune = get_post_meta( $post_id, $config['meta_prefix'] . 'commune', true );
        $region  = get_post_meta( $post_id, $config['meta_prefix'] . 'region', true );

        if ( ! $commune ) return $content;

        $related_html = $this->build_related_cities_html( $country_key, $commune, $region );

        // Insert before CTA if present, otherwise append
        if ( strpos( $content, '<!-- GeoBot CTA -->' ) !== false ) {
            return str_replace( '<!-- GeoBot CTA -->', $related_html . "\n\n<!-- GeoBot CTA -->", $content );
        }

        return $content . $related_html;
    }

    /**
     * Detect which country a GeoBot post belongs to.
     *
     * @param int $post_id
     * @return string|false Country key or false.
     */
    public function detect_post_country( int $post_id ) {
        foreach ( DBA_GeoBot_Registry::get_countries() as $key => $config ) {
            $meta_key = $config['meta_prefix'] . 'auto_created';
            if ( get_post_meta( $post_id, $meta_key, true ) ) {
                return $key;
            }
        }
        return false;
    }

    /* =================================================================
     *  HUB / PILLAR PAGE — Country-level index of ALL city posts
     * ================================================================= */

    /**
     * Get the hub page ID for a country.
     */
    public function get_hub_page_id( string $country_key ) {
        return (int) get_option( 'dba_geo_' . $country_key . self::HUB_PAGE_OPTION_SUFFIX, 0 );
    }

    /**
     * Create or update the hub/pillar page for a country.
     * Groups all city posts by region, with links to each.
     *
     * @param string $country_key
     * @return int|WP_Error  Page ID or error.
     */
    public function create_or_update_hub_page( string $country_key, array $extra_category_ids = array() ) {
        $config = DBA_GeoBot_Registry::get_country( $country_key );
        if ( ! $config ) return new WP_Error( 'invalid_country', 'Unknown country' );

        $all_posts = $this->get_country_posts( $country_key );

        // Group by region
        $regions = array();
        foreach ( $all_posts as $commune => $data ) {
            $region = $data['region'] ?: 'Autres';
            $regions[ $region ][] = $data;
        }
        ksort( $regions );

        // Sort cities within each region alphabetically
        foreach ( $regions as &$cities ) {
            usort( $cities, function( $a, $b ) {
                return strcmp( $a['commune'], $b['commune'] );
            } );
        }
        unset( $cities );

        // Build hub page content
        $hub_content = $this->build_hub_content( $config, $regions, count( $all_posts ) );

        // Localized page titles
        $hub_titles = array(
            'fr' => 'Investir en France : nos analyses par ville',
            'it' => 'Investire in Italia: le nostre analisi per città',
            'es' => 'Invertir en España: nuestros análisis por ciudad',
            'pt' => 'Investir em Portugal: as nossas análises por cidade',
        );
        $lang = $config['language'] ?? 'fr';
        $title = $hub_titles[ $lang ] ?? $hub_titles['fr'];

        $hub_slugs = array(
            'fr' => 'investir-en-france-analyses-par-ville',
            'it' => 'investire-in-italia-analisi-per-citta',
            'es' => 'invertir-en-espana-analisis-por-ciudad',
            'pt' => 'investir-em-portugal-analises-por-cidade',
        );
        $slug = $hub_slugs[ $lang ] ?? $hub_slugs['fr'];

        $page_id = $this->get_hub_page_id( $country_key );

        $page_args = array(
            'post_title'   => $title,
            'post_content' => $hub_content,
            'post_name'    => $slug,
            'post_status'  => 'publish',
            'post_type'    => 'post',
            'post_author'  => 1,
        );

        if ( $page_id && ( $existing = get_post( $page_id ) ) && in_array( $existing->post_status, array( 'publish', 'draft', 'pending', 'future' ), true ) ) {
            // Update existing hub post
            $page_args['ID'] = $page_id;
            $result = wp_update_post( $page_args, true );
        } else {
            // Create new hub post (old one was deleted/trashed or never existed)
            $result = wp_insert_post( $page_args, true );
            if ( ! is_wp_error( $result ) ) {
                update_option( 'dba_geo_' . $country_key . self::HUB_PAGE_OPTION_SUFFIX, $result, false );
                $page_id = $result;
            }
        }

        if ( is_wp_error( $result ) ) return $result;

        // Set WPML language
        if ( function_exists( 'wpml_set_element_language_details' ) ) {
            $trid = apply_filters( 'wpml_element_trid', null, $page_id, 'post_post' );
            do_action( 'wpml_set_element_language_details', array(
                'element_id'    => $page_id,
                'element_type'  => 'post_post',
                'trid'          => $trid,
                'language_code' => $config['wpml_code'],
            ) );
        }

        // Set hub-specific meta
        update_post_meta( $page_id, '_dba_geo_hub_page', $country_key );
        update_post_meta( $page_id, '_dba_geo_hub_updated', current_time( 'mysql' ) );
        update_post_meta( $page_id, '_dba_geo_hub_city_count', count( $all_posts ) );

        // Assign to same WP category as city posts + any extra user-selected categories
        $cat_name = $config['wp_category'];
        $term = term_exists( $cat_name, 'category' );
        if ( ! $term ) $term = wp_insert_term( $cat_name, 'category' );
        $cat_ids = array();
        if ( ! is_wp_error( $term ) ) {
            $tid = is_array( $term ) ? $term['term_id'] : $term;
            $cat_ids[] = intval( $tid );
        }
        // Merge extra categories from admin selection
        if ( ! empty( $extra_category_ids ) ) {
            $cat_ids = array_unique( array_merge( $cat_ids, array_map( 'intval', $extra_category_ids ) ) );
        }
        // Also restore any previously saved extra categories (for updates without re-selecting)
        if ( empty( $extra_category_ids ) && $page_id ) {
            $saved_extras = get_post_meta( $page_id, '_dba_geo_hub_extra_categories', true );
            if ( is_array( $saved_extras ) && ! empty( $saved_extras ) ) {
                $cat_ids = array_unique( array_merge( $cat_ids, array_map( 'intval', $saved_extras ) ) );
            }
        }
        if ( ! empty( $cat_ids ) ) {
            wp_set_post_categories( $page_id, $cat_ids, false );
        }
        // Save extra categories for future reference
        if ( ! empty( $extra_category_ids ) ) {
            update_post_meta( $page_id, '_dba_geo_hub_extra_categories', $extra_category_ids );
        }

        // Yoast SEO for hub page
        $hub_seo = array(
            'fr' => array(
                'focus'    => 'investir en France',
                'seo'      => 'Investir en France : analyses par ville | Damalion',
                'meta'     => 'Découvrez nos analyses d\'investissement ville par ville en France. ' . count( $all_posts ) . ' villes analysées par les experts Damalion.',
            ),
            'it' => array(
                'focus'    => 'investire in Italia',
                'seo'      => 'Investire in Italia: analisi per città | Damalion',
                'meta'     => 'Scoprite le nostre analisi di investimento città per città in Italia. ' . count( $all_posts ) . ' città analizzate dagli esperti Damalion.',
            ),
            'es' => array(
                'focus'    => 'invertir en España',
                'seo'      => 'Invertir en España: análisis por ciudad | Damalion',
                'meta'     => 'Descubra nuestros análisis de inversión ciudad por ciudad en España. ' . count( $all_posts ) . ' ciudades analizadas por los expertos de Damalion.',
            ),
            'pt' => array(
                'focus'    => 'investir em Portugal',
                'seo'      => 'Investir em Portugal: análises por cidade | Damalion',
                'meta'     => 'Descubra as nossas análises de investimento cidade por cidade em Portugal. ' . count( $all_posts ) . ' cidades analisadas pelos especialistas da Damalion.',
            ),
        );
        $seo = $hub_seo[ $lang ] ?? $hub_seo['fr'];

        update_post_meta( $page_id, '_yoast_wpseo_focuskw',            $seo['focus'] );
        update_post_meta( $page_id, '_yoast_wpseo_title',              $seo['seo'] );
        update_post_meta( $page_id, '_yoast_wpseo_metadesc',           $seo['meta'] );
        update_post_meta( $page_id, '_yoast_wpseo_opengraph-title',    $seo['seo'] );
        update_post_meta( $page_id, '_yoast_wpseo_opengraph-description', $seo['meta'] );

        error_log( "DBA GeoBot [{$country_key}]: Hub page updated — #{$page_id} with " . count( $all_posts ) . " cities in " . count( $regions ) . " regions." );

        return $page_id;
    }

    /**
     * Build the HTML content for the hub/pillar page.
     */
    private function build_hub_content( array $config, array $regions, int $total_cities ) {
        $lang = $config['language'] ?? 'fr';

        // Intro paragraphs per language
        $intros = array(
            'fr' => '<p>Damalion publie des analyses d\'investissement détaillées pour les principales villes de France. Chaque analyse couvre les fondamentaux démographiques, les secteurs économiques clés, la présence des entreprises, les projets de développement urbain et les perspectives d\'investissement.</p>'
                . '<p>Consultez ci-dessous l\'ensemble de nos ' . $total_cities . ' analyses, organisées par région.</p>',
            'it' => '<p>Damalion pubblica analisi dettagliate sugli investimenti per le principali città italiane. Ogni analisi copre i fondamentali demografici, i settori economici chiave, la presenza aziendale, i progetti di sviluppo urbano e le prospettive di investimento.</p>'
                . '<p>Consultate di seguito tutte le nostre ' . $total_cities . ' analisi, organizzate per regione.</p>',
            'es' => '<p>Damalion publica análisis detallados de inversión para las principales ciudades de España. Cada análisis cubre los fundamentos demográficos, los sectores económicos clave, la presencia empresarial, los proyectos de desarrollo urbano y las perspectivas de inversión.</p>'
                . '<p>Consulte a continuación todos nuestros ' . $total_cities . ' análisis, organizados por región.</p>',
            'pt' => '<p>A Damalion publica análises detalhadas de investimento para as principais cidades de Portugal. Cada análise abrange os fundamentos demográficos, os setores económicos chave, a presença empresarial, os projetos de desenvolvimento urbano e as perspetivas de investimento.</p>'
                . '<p>Consulte abaixo todas as nossas ' . $total_cities . ' análises, organizadas por região.</p>',
        );

        $html = $intros[ $lang ] ?? $intros['fr'];
        $html .= "\n\n";

        // Table of contents (region anchors)
        $toc_label = array( 'fr' => 'Sommaire', 'it' => 'Sommario', 'es' => 'Índice', 'pt' => 'Índice' );
        $html .= '<div class="dba-geo-hub-toc" style="background:#f0f4f8;padding:20px 25px;border-radius:8px;margin-bottom:30px;">' . "\n";
        $html .= '<h3 style="margin:0 0 10px;font-size:16px;">' . esc_html( $toc_label[ $lang ] ?? 'Sommaire' ) . '</h3>' . "\n";
        $html .= '<div style="column-count:3;column-gap:20px;">' . "\n";
        foreach ( $regions as $region => $cities ) {
            $anchor = sanitize_title( $region );
            $html .= '<p style="margin:0 0 4px;"><a href="#region-' . esc_attr( $anchor ) . '" style="text-decoration:none;">'
                . esc_html( $region ) . ' <span style="color:#999;">(' . count( $cities ) . ')</span></a></p>' . "\n";
        }
        $html .= '</div>' . "\n";
        $html .= '</div>' . "\n\n";

        // Each region section
        foreach ( $regions as $region => $cities ) {
            $anchor = sanitize_title( $region );
            $html .= '<h2 id="region-' . esc_attr( $anchor ) . '">' . esc_html( $region ) . ' (' . count( $cities ) . ')</h2>' . "\n";
            $html .= '<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:30px;">' . "\n";

            foreach ( $cities as $city ) {
                $html .= '<div style="background:#fff;border:1px solid #e0e0e0;border-radius:6px;padding:12px 15px;">';
                $html .= '<a href="' . esc_url( $city['permalink'] ) . '" style="text-decoration:none;color:#1565c0;font-weight:600;font-size:15px;">';
                $html .= esc_html( $city['commune'] );
                $html .= '</a>';
                $html .= '</div>' . "\n";
            }

            $html .= '</div>' . "\n\n";
        }

        // CTA at bottom
        if ( ! empty( $config['cta_text'] ) ) {
            $html .= '<div style="margin-top:30px;padding:20px 25px;background:#f0f7ff;border-radius:8px;">' . "\n";
            $html .= '<p>' . esc_html( $config['cta_text'] ) . '</p>' . "\n";
            if ( ! empty( $config['cta_link_url'] ) && ! empty( $config['cta_link_text'] ) ) {
                $html .= '<p><a href="' . esc_url( $config['cta_link_url'] ) . '" style="font-weight:600;color:#1565c0;">'
                    . esc_html( $config['cta_link_text'] ) . '</a></p>' . "\n";
            }
            $html .= '</div>' . "\n";
        }

        return $html;
    }

    /* =================================================================
     *  BREADCRUMB SCHEMA — JSON-LD for Google
     * ================================================================= */

    /**
     * Inject BreadcrumbList JSON-LD schema for GeoBot posts.
     * Format: Home > Investissements > {Region} > {City}
     */
    public function inject_breadcrumb_schema() {
        if ( ! is_singular( 'post' ) ) return;

        $post_id = get_the_ID();
        if ( ! $post_id ) return;

        $country_key = $this->detect_post_country( $post_id );
        if ( ! $country_key ) return;

        $config  = DBA_GeoBot_Registry::get_country( $country_key );
        $commune = get_post_meta( $post_id, $config['meta_prefix'] . 'commune', true );
        $region  = get_post_meta( $post_id, $config['meta_prefix'] . 'region', true );

        if ( ! $commune ) return;

        $hub_page_id = $this->get_hub_page_id( $country_key );
        $hub_url     = $hub_page_id ? get_permalink( $hub_page_id ) : '';

        $breadcrumbs = array(
            array(
                '@type'    => 'ListItem',
                'position' => 1,
                'name'     => 'Home',
                'item'     => home_url( '/' ),
            ),
        );

        // Level 2: Category (Investissements)
        $cat = get_term_by( 'name', $config['wp_category'], 'category' );
        if ( $cat ) {
            $breadcrumbs[] = array(
                '@type'    => 'ListItem',
                'position' => 2,
                'name'     => $cat->name,
                'item'     => get_term_link( $cat ),
            );
        }

        // Level 3: Hub page (country-level)
        if ( $hub_url ) {
            $hub_titles = array(
                'fr' => 'France', 'it' => 'Italia', 'es' => 'España', 'pt' => 'Portugal',
            );
            $breadcrumbs[] = array(
                '@type'    => 'ListItem',
                'position' => 3,
                'name'     => $hub_titles[ $config['language'] ] ?? ucfirst( $country_key ),
                'item'     => $hub_url,
            );
        }

        // Level 4: Region (anchor on hub page)
        if ( $region ) {
            $region_url = $hub_url ? $hub_url . '#region-' . sanitize_title( $region ) : '';
            $breadcrumbs[] = array(
                '@type'    => 'ListItem',
                'position' => count( $breadcrumbs ) + 1,
                'name'     => $region,
                'item'     => $region_url ?: home_url( '/' ),
            );
        }

        // Level 5: Current city post
        $breadcrumbs[] = array(
            '@type'    => 'ListItem',
            'position' => count( $breadcrumbs ) + 1,
            'name'     => $commune,
            'item'     => get_permalink( $post_id ),
        );

        $schema = array(
            '@context'        => 'https://schema.org',
            '@type'           => 'BreadcrumbList',
            'itemListElement' => $breadcrumbs,
        );

        echo "\n<!-- GeoBot Breadcrumb Schema -->\n";
        echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ) . '</script>' . "\n";
    }

    /* =================================================================
     *  RETROACTIVE LINKER — Update existing posts with cross-links
     * ================================================================= */

    /**
     * Scan all existing GeoBot posts for a country and inject
     * the "Related Cities" block into post_content for posts that don't have it.
     * Also updates the hub page.
     *
     * @param string $country_key
     * @return array Results summary.
     */
    public function run_retroactive_linking( string $country_key ) {
        $config = DBA_GeoBot_Registry::get_country( $country_key );
        if ( ! $config ) return array( 'error' => 'Invalid country' );

        $meta_key = $config['meta_prefix'] . 'auto_created';

        $posts = get_posts( array(
            'post_type'      => 'post',
            'post_status'    => array( 'publish', 'draft', 'pending', 'future' ),
            'posts_per_page' => 500,
            'meta_query'     => array(
                array( 'key' => $meta_key, 'compare' => 'EXISTS' ),
            ),
            'fields' => 'ids',
        ) );

        $updated = 0;
        $skipped = 0;
        $errors  = 0;

        foreach ( $posts as $pid ) {
            $content = get_post_field( 'post_content', $pid );
            $commune = get_post_meta( $pid, $config['meta_prefix'] . 'commune', true );
            $region  = get_post_meta( $pid, $config['meta_prefix'] . 'region', true );

            if ( ! $commune ) {
                $skipped++;
                continue;
            }

            // Check if already has the linking block (static version)
            if ( strpos( $content, '<!-- GeoBot Internal Linking Block -->' ) !== false ) {
                // Remove old block and regenerate
                $content = preg_replace(
                    '/\s*<!-- GeoBot Internal Linking Block -->.*?<!-- \/GeoBot Internal Linking Block -->/s',
                    '',
                    $content
                );
            }

            // Build fresh related cities block
            $related_html = $this->build_related_cities_html( $country_key, $commune, $region );

            if ( empty( $related_html ) ) {
                $skipped++;
                continue;
            }

            // Insert before CTA if present, otherwise append
            if ( strpos( $content, '<!-- GeoBot CTA -->' ) !== false ) {
                $new_content = str_replace(
                    '<!-- GeoBot CTA -->',
                    $related_html . "\n\n<!-- GeoBot CTA -->",
                    $content
                );
            } else {
                $new_content = $content . $related_html;
            }

            $result = wp_update_post( array(
                'ID'           => $pid,
                'post_content' => $new_content,
            ), true );

            if ( is_wp_error( $result ) ) {
                $errors++;
            } else {
                $updated++;
            }
        }

        // Also update hub post if it exists
        $hub_id = $this->get_hub_page_id( $country_key );
        $hub_status = 'not created';
        if ( $hub_id && get_post_status( $hub_id ) ) {
            $hub_result = $this->create_or_update_hub_page( $country_key );
            $hub_status = is_wp_error( $hub_result ) ? 'error' : 'updated (post #' . $hub_result . ')';
        }

        return array(
            'total_posts' => count( $posts ),
            'updated'     => $updated,
            'skipped'     => $skipped,
            'errors'      => $errors,
            'hub_page'    => $hub_status,
        );
    }

    /**
     * AJAX: Run retroactive linking for a country.
     */
    public function ajax_retroactive_link() {
        check_ajax_referer( 'dba_admin_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized' );

        $country_key = sanitize_key( $_POST['country_key'] ?? '' );
        if ( ! DBA_GeoBot_Registry::get_country( $country_key ) ) {
            wp_send_json_error( 'Invalid country' );
        }

        @set_time_limit( 300 );

        $results = $this->run_retroactive_linking( $country_key );
        wp_send_json_success( $results );
    }

    /**
     * AJAX: Create or update hub page for a country.
     */
    public function ajax_update_hub_page() {
        check_ajax_referer( 'dba_admin_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized' );

        $country_key = sanitize_key( $_POST['country_key'] ?? '' );
        if ( ! DBA_GeoBot_Registry::get_country( $country_key ) ) {
            wp_send_json_error( 'Invalid country' );
        }

        // Extra categories selected by user
        $extra_cat_ids = array();
        if ( ! empty( $_POST['category_ids'] ) ) {
            if ( is_array( $_POST['category_ids'] ) ) {
                $extra_cat_ids = array_map( 'intval', $_POST['category_ids'] );
            } else {
                // Comma-separated fallback
                $extra_cat_ids = array_map( 'intval', explode( ',', $_POST['category_ids'] ) );
            }
            $extra_cat_ids = array_filter( $extra_cat_ids );
        }

        $page_id = $this->create_or_update_hub_page( $country_key, $extra_cat_ids );

        if ( is_wp_error( $page_id ) ) {
            wp_send_json_error( $page_id->get_error_message() );
        }

        wp_send_json_success( array(
            'page_id'    => $page_id,
            'permalink'  => get_permalink( $page_id ),
            'title'      => get_the_title( $page_id ),
            'categories' => wp_get_post_categories( $page_id, array( 'fields' => 'names' ) ),
            'message'    => 'Hub post updated — #' . $page_id,
        ) );
    }

    /* =================================================================
     *  HELPER: Called by generator after creating a new post
     * ================================================================= */

    /**
     * Post-publish hook: update hub page and optionally retroactively link.
     * Called by DBA_GeoBot_Generator after each successful post creation.
     *
     * @param string $country_key
     * @param int    $post_id
     */
    public function on_post_published( string $country_key, int $post_id ) {
        // Only update hub if it already exists (never auto-create)
        $hub_id = $this->get_hub_page_id( $country_key );
        if ( $hub_id && get_post_status( $hub_id ) ) {
            $this->create_or_update_hub_page( $country_key );
        }

        // Add back-links: update the 5 most recently linked posts
        // so they now also link to this new post
        $config  = DBA_GeoBot_Registry::get_country( $country_key );
        $commune = get_post_meta( $post_id, $config['meta_prefix'] . 'commune', true );
        $region  = get_post_meta( $post_id, $config['meta_prefix'] . 'region', true );

        if ( ! $commune ) return;

        // Find posts that should link back to this new one
        $related = $this->find_related_cities( $country_key, $commune, $region, 3 );

        foreach ( $related as $city ) {
            $rpid = $city['post_id'];
            $content = get_post_field( 'post_content', $rpid );

            // Only update if the related block exists (refresh it)
            if ( strpos( $content, '<!-- GeoBot Internal Linking Block -->' ) !== false ) {
                $r_commune = get_post_meta( $rpid, $config['meta_prefix'] . 'commune', true );
                $r_region  = get_post_meta( $rpid, $config['meta_prefix'] . 'region', true );

                // Remove old block
                $content = preg_replace(
                    '/\s*<!-- GeoBot Internal Linking Block -->.*?<!-- \/GeoBot Internal Linking Block -->/s',
                    '',
                    $content
                );

                // Rebuild
                $new_block = $this->build_related_cities_html( $country_key, $r_commune, $r_region );

                if ( strpos( $content, '<!-- GeoBot CTA -->' ) !== false ) {
                    $content = str_replace(
                        '<!-- GeoBot CTA -->',
                        $new_block . "\n\n<!-- GeoBot CTA -->",
                        $content
                    );
                } else {
                    $content .= $new_block;
                }

                wp_update_post( array(
                    'ID'           => $rpid,
                    'post_content' => $content,
                ) );
            }
        }
    }
}
