<?php
/**
 * DBA Eastern Europe Inbound Linker — Site-wide scanner for inbound links TO eeurope articles
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  PROBLEM: Eastern Europe posts are an isolated silo. No PageRank flows
 *  in from the rest of the blog (core EN/FR articles, GeoBot, LuxStrat,
 *  LATAM, Startup Hub, service pages, translated pages, etc.)
 *
 *  SOLUTION: Scans the ENTIRE site — every post, every page, every
 *  language — and auto-links city name mentions and investment keyword
 *  phrases to Eastern Europe articles.
 *
 *  SCAN STRATEGY (3-tier):
 *    Tier 1 — English-language WPML posts/pages
 *    Tier 2 — URL pattern match:
 *      Posts/pages whose slug contains EE-related terms
 *      (e.g. "poland", "czech", "romania", "eastern-europe"...)
 *    Tier 3 — Full site sweep:
 *      Every remaining published post/page on the entire site
 *
 *  LINK TARGETS (what we link TO):
 *    - Published EEurope city posts (city name as anchor)
 *    - Country name + investment variant phrases
 *
 *  EXCLUSIONS (never scanned):
 *    - EEurope's own posts (no self-linking)
 *
 *  LINK RULES:
 *    - Only first safe mention of each city per post
 *    - Max 3 EEurope links per post (avoid over-optimization)
 *    - Never link inside <a>, <h1>–<h4>, <script>, <style>, comments
 *    - City names < 4 chars skipped (no false positives)
 *    - Case-insensitive match, original case preserved
 *    - All links tagged with class="dba-eeurope-inbound" (fully reversible)
 *
 *  ZERO CONFLICT: Own meta key (_dba_eeurope_inbound_linked), own CSS class.
 *  Mirrors Startup Hub / GeoBot Inbound architecture exactly.
 * ═══════════════════════════════════════════════════════════════════════
 *
 * @since 3.0.0
 */

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

class DBA_EEurope_Inbound {

    private static $instance = null;

    /** Max EEurope inbound links per post */
    const MAX_LINKS_PER_POST = 3;

    /** Min city name length (skip short names) */
    const MIN_CITY_NAME_LENGTH = 4;

    /** Batch size for DB queries */
    const SCAN_BATCH_SIZE = 50;

    /** Max posts per scan run (safety valve) */
    const MAX_SCAN_POSTS = 2000;

    /** CSS class for reversible link identification */
    const LINK_CLASS = 'dba-eeurope-inbound';

    /** Meta key to mark scanned posts */
    const META_KEY = '_dba_eeurope_inbound_linked';

    /**
     * Eastern-Europe-related URL slug patterns for Tier 2 matching.
     */
    private static $url_patterns = array(
        'poland', 'polish', 'warsaw', 'krakow', 'wroclaw',
        'czech', 'prague', 'brno',
        'romania', 'bucharest', 'cluj',
        'hungary', 'budapest',
        'croatia', 'zagreb',
        'slovenia', 'ljubljana',
        'slovakia', 'bratislava',
        'bulgaria', 'sofia',
        'serbia', 'belgrade',
        'estonia', 'tallinn',
        'latvia', 'riga',
        'lithuania', 'vilnius',
        'eastern-europe', 'central-europe', 'cee',
        'eu-investment', 'eu-market', 'eu-expansion',
        'company-formation', 'business-registration',
        'invest-in-europe', 'european-market',
        'fdi', 'foreign-direct-investment',
    );

    /**
     * Get URL patterns (public accessor for admin display).
     */
    public static function get_url_patterns() {
        return self::$url_patterns;
    }

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

    private function __construct() {
        add_action( 'wp_ajax_dba_eeurope_inbound_scan',       array( $this, 'ajax_run_scan' ) );
        add_action( 'wp_ajax_dba_eeurope_inbound_scan_stats', array( $this, 'ajax_get_stats' ) );
        add_action( 'wp_ajax_dba_eeurope_inbound_strip',      array( $this, 'ajax_strip_links' ) );
        add_action( 'wp_ajax_dba_eeurope_inbound_progress',   array( $this, 'ajax_get_progress' ) );
    }

    /** Transient key for live progress */
    const PROGRESS_KEY = 'dba_eeurope_inbound_progress';

    private function update_progress( array $data ) {
        set_transient( self::PROGRESS_KEY, $data, 600 );
    }

    private function clear_progress() {
        delete_transient( self::PROGRESS_KEY );
    }

    /* =================================================================
     *  LINK TARGET INDEX — What we link TO
     * ================================================================= */

    /**
     * Build a lookup table of link targets:
     *   phrase (lowercase) → { permalink, post_id, city, type }
     *
     * Includes:
     *   - Published EEurope city posts (city name as anchor)
     *   - Country + investment variant phrases
     *
     * @return array Keyed by lowercase phrase, sorted longest-first.
     */
    public function build_link_targets() {
        $targets = array();

        /* ── City posts ── */
        $posts = get_posts( array(
            'post_type'      => 'post',
            'post_status'    => 'publish',
            'posts_per_page' => 500,
            'meta_query'     => array(
                array( 'key' => '_dba_eeurope_auto_created', 'compare' => 'EXISTS' ),
            ),
            'fields' => 'ids',
        ) );

        foreach ( $posts as $pid ) {
            $city    = get_post_meta( $pid, '_dba_eeurope_city', true );
            $country = get_post_meta( $pid, '_dba_eeurope_country', true );
            if ( ! $city ) continue;
            if ( mb_strlen( $city, 'UTF-8' ) < self::MIN_CITY_NAME_LENGTH ) continue;

            $key = mb_strtolower( trim( $city ), 'UTF-8' );
            if ( ! isset( $targets[ $key ] ) ) {
                $targets[ $key ] = array(
                    'post_id'   => $pid,
                    'phrase'    => $city,
                    'permalink' => get_permalink( $pid ),
                    'title'     => get_the_title( $pid ),
                    'type'      => 'city',
                );
            }

            // Variants: "City investment" and "City business"
            foreach ( array( 'investment', 'business', 'company formation' ) as $suffix ) {
                $variant = $city . ' ' . $suffix;
                $vk = mb_strtolower( $variant, 'UTF-8' );
                if ( ! isset( $targets[ $vk ] ) ) {
                    $targets[ $vk ] = array(
                        'post_id' => $pid, 'phrase' => $variant,
                        'permalink' => get_permalink( $pid ),
                        'title' => get_the_title( $pid ), 'type' => 'city_variant',
                    );
                }
            }

            // Country variant: "invest in Poland" etc.
            if ( $country ) {
                $countries = DBA_EEurope_Registry::get_countries();
                $country_label = isset( $countries[ $country ] ) ? $countries[ $country ]['label'] : '';
                if ( $country_label ) {
                    foreach ( array( 'invest in ' . $country_label, $country_label . ' investment', 'business in ' . $country_label ) as $cp ) {
                        $cpk = mb_strtolower( $cp, 'UTF-8' );
                        if ( ! isset( $targets[ $cpk ] ) ) {
                            $targets[ $cpk ] = array(
                                'post_id' => $pid, 'phrase' => $cp,
                                'permalink' => get_permalink( $pid ),
                                'title' => get_the_title( $pid ), 'type' => 'country_variant',
                            );
                        }
                    }
                }
            }

            // Theme variant: "real estate in Warsaw"
            $theme_key = get_post_meta( $pid, '_dba_eeurope_theme', true );
            if ( $theme_key ) {
                $theme_label = str_replace( '_', ' ', $theme_key );
                $tp = $theme_label . ' in ' . $city;
                $tpk = mb_strtolower( $tp, 'UTF-8' );
                if ( mb_strlen( $tp, 'UTF-8' ) >= self::MIN_CITY_NAME_LENGTH && ! isset( $targets[ $tpk ] ) ) {
                    $targets[ $tpk ] = array(
                        'post_id' => $pid, 'phrase' => $tp,
                        'permalink' => get_permalink( $pid ),
                        'title' => get_the_title( $pid ), 'type' => 'theme_city',
                    );
                }
            }
        }

        // Sort by phrase length descending (match "Warsaw investment" before "Warsaw")
        uasort( $targets, function( $a, $b ) {
            return mb_strlen( $b['phrase'] ) - mb_strlen( $a['phrase'] );
        } );

        return $targets;
    }

    /* =================================================================
     *  BUILD EXCLUDE LIST — Posts we never scan
     * ================================================================= */

    private function build_exclude_ids() {
        $exclude = array();

        // All EEurope posts
        $ee_posts = get_posts( array(
            'post_type'      => 'post',
            'post_status'    => 'any',
            'posts_per_page' => 1000,
            'meta_query'     => array(
                array( 'key' => '_dba_eeurope_auto_created', 'compare' => 'EXISTS' ),
            ),
            'fields' => 'ids',
        ) );
        $exclude = array_merge( $exclude, $ee_posts );

        return array_unique( array_filter( $exclude ) );
    }

    /* =================================================================
     *  WPML HELPERS
     * ================================================================= */

    private function get_posts_by_language( string $lang_code, array $exclude_ids = array() ) {
        global $wpdb;

        $icl_table = $wpdb->prefix . 'icl_translations';
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$icl_table}'" ) !== $icl_table ) {
            return array();
        }

        $exclude_str = '';
        if ( ! empty( $exclude_ids ) ) {
            $exclude_str = ' AND p.ID NOT IN (' . implode( ',', array_map( 'intval', $exclude_ids ) ) . ')';
        }

        $results = $wpdb->get_col( $wpdb->prepare(
            "SELECT p.ID FROM {$wpdb->posts} p
             INNER JOIN {$icl_table} t ON t.element_id = p.ID
             WHERE t.language_code = %s
             AND t.element_type IN ('post_post', 'post_page')
             AND p.post_status = 'publish'
             {$exclude_str}
             ORDER BY p.post_date DESC
             LIMIT 500",
            $lang_code
        ) );

        return array_map( 'intval', $results ?: array() );
    }

    private function get_posts_by_url_pattern( array $exclude_ids = array() ) {
        global $wpdb;

        if ( empty( self::$url_patterns ) ) return array();

        $like_clauses = array();
        foreach ( self::$url_patterns as $pat ) {
            $like_clauses[] = $wpdb->prepare( "p.post_name LIKE %s", '%' . $wpdb->esc_like( $pat ) . '%' );
        }

        $where_likes = '(' . implode( ' OR ', $like_clauses ) . ')';

        $exclude_str = '';
        if ( ! empty( $exclude_ids ) ) {
            $exclude_str = ' AND p.ID NOT IN (' . implode( ',', array_map( 'intval', $exclude_ids ) ) . ')';
        }

        $results = $wpdb->get_col(
            "SELECT p.ID FROM {$wpdb->posts} p
             WHERE {$where_likes}
             AND p.post_type IN ('post', 'page')
             AND p.post_status = 'publish'
             {$exclude_str}
             ORDER BY p.post_date DESC
             LIMIT 500"
        );

        return array_map( 'intval', $results ?: array() );
    }

    /* =================================================================
     *  MAIN SCANNER
     * ================================================================= */

    public function run_scan( bool $force_rescan = false ) {

        @set_time_limit( 600 );
        @ini_set( 'memory_limit', '512M' );

        $targets = $this->build_link_targets();

        if ( empty( $targets ) ) {
            return array(
                'error'          => 'No link targets found. Need at least one published Eastern Europe city post.',
                'scanned'        => 0,
                'linked'         => 0,
                'links_injected' => 0,
                'targets_count'  => 0,
            );
        }

        $exclude_ids = $this->build_exclude_ids();

        $this->update_progress( array(
            'phase'   => 'building_queue',
            'detail'  => 'Building scan queue (Tier 1: WPML EN)…',
            'scanned' => 0, 'total' => 0, 'linked' => 0, 'percent' => 5,
        ) );

        // ── Build prioritized scan queue ──
        $scan_queue = array();
        $queued_ids = array();

        // Tier 1: English WPML posts
        $en_posts = $this->get_posts_by_language( 'en', $exclude_ids );
        foreach ( $en_posts as $pid ) {
            if ( ! isset( $queued_ids[ $pid ] ) ) {
                $scan_queue[] = array( 'id' => $pid, 'tier' => 1 );
                $queued_ids[ $pid ] = true;
            }
        }

        $this->update_progress( array(
            'phase'   => 'building_queue',
            'detail'  => 'Tier 2: URL pattern matching (eastern-europe, poland, czech…)…',
            'scanned' => 0, 'total' => count( $scan_queue ), 'linked' => 0, 'percent' => 8,
        ) );

        // Tier 2: URL pattern match (poland, czech, romania, etc.)
        $url_posts = $this->get_posts_by_url_pattern( $exclude_ids );
        foreach ( $url_posts as $pid ) {
            if ( ! isset( $queued_ids[ $pid ] ) ) {
                $scan_queue[] = array( 'id' => $pid, 'tier' => 2 );
                $queued_ids[ $pid ] = true;
            }
        }

        $this->update_progress( array(
            'phase'   => 'building_queue',
            'detail'  => 'Tier 3: Full site sweep…',
            'scanned' => 0, 'total' => count( $scan_queue ), 'linked' => 0, 'percent' => 12,
        ) );

        // Tier 3: Full site sweep
        $page = 1;
        $tier3_count = 0;

        while ( $tier3_count < self::MAX_SCAN_POSTS ) {
            $meta_query = array();
            if ( ! $force_rescan ) {
                $meta_query[] = array(
                    'key'     => self::META_KEY,
                    'compare' => 'NOT EXISTS',
                );
            }

            $all_ids_to_exclude = array_merge( $exclude_ids, array_keys( $queued_ids ) );

            $remaining = get_posts( array(
                'post_type'      => array( 'post', 'page' ),
                'post_status'    => 'publish',
                'posts_per_page' => self::SCAN_BATCH_SIZE,
                'paged'          => $page,
                'post__not_in'   => $all_ids_to_exclude,
                'meta_query'     => $meta_query,
                'orderby'        => 'date',
                'order'          => 'DESC',
                'fields'         => 'ids',
            ) );

            if ( empty( $remaining ) ) break;

            foreach ( $remaining as $pid ) {
                if ( ! isset( $queued_ids[ $pid ] ) ) {
                    $scan_queue[] = array( 'id' => $pid, 'tier' => 3 );
                    $queued_ids[ $pid ] = true;
                    $tier3_count++;
                }
            }

            $page++;
            if ( $page > 100 ) break;
        }

        // ── Process queue ──
        $total_queue = count( $scan_queue );

        $results = array(
            'scanned'        => 0,
            'linked'         => 0,
            'links_injected' => 0,
            'skipped'        => 0,
            'targets_count'  => count( $targets ),
            'tier1_scanned'  => 0,
            'tier2_scanned'  => 0,
            'tier3_scanned'  => 0,
            'details'        => array(),
        );

        $this->update_progress( array(
            'phase'   => 'scanning',
            'detail'  => 'Scanning ' . $total_queue . ' posts (' . count( $targets ) . ' link targets)…',
            'scanned' => 0, 'total' => $total_queue, 'linked' => 0, 'percent' => 15,
        ) );

        foreach ( $scan_queue as $idx => $item ) {
            $pid  = $item['id'];
            $tier = $item['tier'];

            $results['scanned']++;
            $results[ 'tier' . $tier . '_scanned' ]++;

            $scan_result = $this->scan_single_post( $pid, $targets, $force_rescan );

            if ( $scan_result['links_added'] > 0 ) {
                $results['linked']++;
                $results['links_injected'] += $scan_result['links_added'];
                $results['details'][] = array(
                    'post_id' => $pid,
                    'title'   => get_the_title( $pid ),
                    'tier'    => $tier,
                    'links'   => $scan_result['phrases_linked'],
                );
            } else {
                $results['skipped']++;
            }

            // Update progress every 10 posts
            if ( $results['scanned'] % 10 === 0 || $results['scanned'] === $total_queue ) {
                $pct = $total_queue > 0
                    ? 15 + (int) round( ( $results['scanned'] / $total_queue ) * 83 )
                    : 100;
                $this->update_progress( array(
                    'phase'   => 'scanning',
                    'detail'  => 'Scanned ' . $results['scanned'] . '/' . $total_queue
                        . ' — Linked ' . $results['linked'] . ' posts ('
                        . $results['links_injected'] . ' links injected)',
                    'scanned' => $results['scanned'],
                    'total'   => $total_queue,
                    'linked'  => $results['linked'],
                    'links'   => $results['links_injected'],
                    'percent' => min( $pct, 98 ),
                ) );
            }

            if ( $results['scanned'] >= self::MAX_SCAN_POSTS ) break;
        }

        error_log( "DBA EEurope Inbound: Scan complete — "
            . "Targets: {$results['targets_count']}, "
            . "Scanned: {$results['scanned']} (T1:{$results['tier1_scanned']} T2:{$results['tier2_scanned']} T3:{$results['tier3_scanned']}), "
            . "Linked: {$results['linked']}, Links: {$results['links_injected']}" );

        $this->update_progress( array(
            'phase'   => 'done',
            'detail'  => 'Complete! Scanned ' . $results['scanned'] . ' posts, linked ' . $results['linked'] . ', injected ' . $results['links_injected'] . ' links.',
            'scanned' => $results['scanned'],
            'total'   => $total_queue,
            'linked'  => $results['linked'],
            'links'   => $results['links_injected'],
            'percent' => 100,
        ) );

        return $results;
    }

    /**
     * Scan a single post and inject city/country links.
     */
    private function scan_single_post( int $post_id, array $targets, bool $force_rescan = false ) {

        $result = array( 'links_added' => 0, 'phrases_linked' => array() );

        $content = get_post_field( 'post_content', $post_id );
        if ( empty( trim( $content ) ) ) return $result;

        // Strip existing inbound links if force rescan
        if ( $force_rescan ) {
            $content = $this->strip_inbound_links( $content );
        }

        $original_content = $content;
        $links_added = 0;

        foreach ( $targets as $data ) {
            if ( $links_added >= self::MAX_LINKS_PER_POST ) break;

            $phrase    = $data['phrase'];
            $permalink = $data['permalink'];

            // Quick check: does this phrase even appear?
            if ( mb_stripos( $content, $phrase ) === false ) continue;

            // Already linked to this target?
            if ( strpos( $content, $permalink ) !== false ) continue;

            // Inject link at first safe occurrence
            $new_content = $this->link_first_safe_mention( $content, $phrase, $permalink );

            if ( $new_content !== $content ) {
                $content = $new_content;
                $links_added++;
                $result['phrases_linked'][] = $phrase;
            }
        }

        if ( $content !== $original_content ) {
            $update = wp_update_post( array(
                'ID'           => $post_id,
                'post_content' => $content,
            ), true );

            if ( is_wp_error( $update ) ) {
                error_log( "DBA EEurope Inbound: Failed to update post #{$post_id}: " . $update->get_error_message() );
                return $result;
            }

            update_post_meta( $post_id, self::META_KEY, wp_json_encode( array(
                'date'    => current_time( 'Y-m-d H:i:s' ),
                'phrases' => $result['phrases_linked'],
                'count'   => $links_added,
            ) ) );

            $result['links_added'] = $links_added;
        }

        return $result;
    }

    /* =================================================================
     *  SAFE HTML LINK INJECTION
     * ================================================================= */

    private function link_first_safe_mention( string $content, string $phrase, string $permalink ) {

        $escaped = preg_quote( $phrase, '/' );
        $pattern = '/(?<![\w\p{L}\p{M}])(' . $escaped . ')(?![\w\p{L}\p{M}])/iu';

        $unsafe_pattern = '/'
            . '(<a\s[^>]*>.*?<\/a>'
            . '|<h[1-4][^>]*>.*?<\/h[1-4]>'
            . '|<script[\s>].*?<\/script>'
            . '|<style[\s>].*?<\/style>'
            . '|<!--.*?-->'
            . '|<[^>]+>'
            . ')/is';

        $parts = preg_split( $unsafe_pattern, $content, -1, PREG_SPLIT_DELIM_CAPTURE );

        $linked  = false;
        $rebuilt = '';

        foreach ( $parts as $part ) {
            if ( $linked ) {
                $rebuilt .= $part;
                continue;
            }

            if ( preg_match( '/^<(?:a\s|\/a>|h[1-4]|\/h[1-4]|script|\/script|style|\/style|!--)/i', $part )
                 || preg_match( '/^<[^>]+>$/s', $part ) ) {
                $rebuilt .= $part;
            } else {
                $link_html = '<a href="' . esc_url( $permalink ) . '" title="${1}" class="' . self::LINK_CLASS . '">${1}</a>';
                $new_part  = preg_replace( $pattern, $link_html, $part, 1, $count );

                if ( $count > 0 ) {
                    $rebuilt .= $new_part;
                    $linked = true;
                } else {
                    $rebuilt .= $part;
                }
            }
        }

        return $rebuilt;
    }

    /**
     * Strip all EEurope inbound links (identified by class="dba-eeurope-inbound").
     * Preserves anchor text.
     */
    public function strip_inbound_links( string $content ) {
        return preg_replace(
            '/<a\s[^>]*class="[^"]*' . preg_quote( self::LINK_CLASS, '/' ) . '[^"]*"[^>]*>(.*?)<\/a>/is',
            '$1',
            $content
        );
    }

    /* =================================================================
     *  STATS — Admin display
     * ================================================================= */

    public function get_scan_stats() {

        $linked_posts = get_posts( array(
            'post_type'      => array( 'post', 'page' ),
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'meta_query'     => array(
                array( 'key' => self::META_KEY, 'compare' => 'EXISTS' ),
            ),
            'fields' => 'ids',
        ) );

        $total_links  = 0;
        $phrase_counts = array();

        foreach ( $linked_posts as $pid ) {
            $meta = json_decode( get_post_meta( $pid, self::META_KEY, true ), true );
            if ( $meta ) {
                $total_links += ( $meta['count'] ?? 0 );
                foreach ( ( $meta['phrases'] ?? array() ) as $phrase ) {
                    $phrase_counts[ $phrase ] = ( $phrase_counts[ $phrase ] ?? 0 ) + 1;
                }
            }
        }

        $targets = $this->build_link_targets();
        arsort( $phrase_counts );

        return array(
            'posts_with_inbound_links' => count( $linked_posts ),
            'total_inbound_links'      => $total_links,
            'link_targets'             => count( $targets ),
            'phrases_with_inbound'     => count( $phrase_counts ),
            'top_linked_phrases'       => array_slice( $phrase_counts, 0, 10, true ),
        );
    }

    /* =================================================================
     *  AJAX HANDLERS
     * ================================================================= */

    public function ajax_run_scan() {
        check_ajax_referer( 'dba_admin_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized' );

        $force_rescan = ! empty( $_POST['force_rescan'] );
        $this->clear_progress();
        $this->update_progress( array(
            'phase' => 'starting', 'detail' => 'Initializing scan…',
            'scanned' => 0, 'total' => 0, 'linked' => 0, 'percent' => 2,
        ) );

        $results = $this->run_scan( $force_rescan );
        wp_send_json_success( $results );
    }

    public function ajax_get_progress() {
        check_ajax_referer( 'dba_admin_nonce', 'nonce' );
        $progress = get_transient( self::PROGRESS_KEY );
        if ( ! $progress ) {
            wp_send_json_success( array(
                'phase' => 'idle', 'detail' => '', 'scanned' => 0,
                'total' => 0, 'linked' => 0, 'percent' => 0,
            ) );
        }
        wp_send_json_success( $progress );
    }

    public function ajax_get_stats() {
        check_ajax_referer( 'dba_admin_nonce', 'nonce' );
        wp_send_json_success( $this->get_scan_stats() );
    }

    public function ajax_strip_links() {
        check_ajax_referer( 'dba_admin_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized' );

        @set_time_limit( 300 );

        $linked_posts = get_posts( array(
            'post_type'      => array( 'post', 'page' ),
            'post_status'    => 'any',
            'posts_per_page' => -1,
            'meta_query'     => array(
                array( 'key' => self::META_KEY, 'compare' => 'EXISTS' ),
            ),
            'fields' => 'ids',
        ) );

        $stripped = 0;
        foreach ( $linked_posts as $pid ) {
            $content     = get_post_field( 'post_content', $pid );
            $new_content = $this->strip_inbound_links( $content );
            if ( $new_content !== $content ) {
                wp_update_post( array(
                    'ID'           => $pid,
                    'post_content' => $new_content,
                ) );
                $stripped++;
            }
            delete_post_meta( $pid, self::META_KEY );
        }

        wp_send_json_success( array(
            'posts_cleaned' => $stripped,
            'meta_removed'  => count( $linked_posts ),
        ) );
    }
}
