<?php
/**
 * DBA Anti-Orphan Cleanup + Third-Party Link Scanner
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  v4 — SCANS ALL NON-ENGLISH POSTS, NOT JUST META-TAGGED ONES
 *
 *  Previous versions only scanned posts with '_dba_anti_orphan_donor_for'
 *  meta. This MISSED translations (NL, DE, ES, PT, IT...) where WPML
 *  synced the corrupted content but the meta was only on the original.
 *
 *  v4 queries:
 *    SELECT p.ID FROM wp_posts p
 *    JOIN wp_icl_translations t ON p.ID = t.element_id
 *    WHERE t.language_code != 'en' AND p.post_status = 'publish'
 *
 *  This catches EVERY non-English published post/page. The regex check
 *  (~5ms per clean post) filters out the ones that don't need fixing.
 *
 *  ALSO HANDLES:
 *  • Posts where meta was deleted by a previous rollback
 *  • Posts where injector crashed before setting meta
 *  • Translated pages created after the injection
 *  • Pages with no anti-orphan meta at all
 *
 *  PERFORMANCE:
 *  • Time-based batching: ~200-300 posts per 25s call
 *  • Cursor-based: WHERE ID > last_id (no OFFSET skip bug)
 *  • Bulk language fetch: one SQL per batch
 *  • Resumable: close tab, come back, click Resume
 *  • Cloudflare-safe: no call exceeds 25 seconds
 * ═══════════════════════════════════════════════════════════════════════
 */

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


/* ██████████████████████████████████████████████████████████████████████
 *  PART 1 — ENGLISH SPAM CLEANUP ENGINE
 * ██████████████████████████████████████████████████████████████████████ */

class DBA_Anti_Orphan_Cleanup {

    const TIME_LIMIT  = 25;
    const FETCH_CHUNK = 300;
    const STATE_KEY   = 'dba_aoc_state';
    const LOG_KEY     = 'dba_anti_orphan_cleanup_log';

    /* ── English injection patterns ── */
    private static function get_patterns() {
        // Group A: sentence + <a> tag (.*? handles <strong>/<em> inside <a>)
        $html = array(
            '/\s*For further insights,?\s*see our guide on\s*<a\s+[^>]*>.*?<\/a>\s*\.?\s*/si',
            '/\s*Learn more about\s*<a\s+[^>]*>.*?<\/a>\s*\.?\s*/si',
            '/\s*You may also find our resource on\s*<a\s+[^>]*>.*?<\/a>\s*helpful\s*\.?\s*/si',
            '/\s*Related:\s*<a\s+[^>]*>.*?<\/a>\s*\.?\s*/si',
            '/<p[^>]*class=["\']dba-see-also["\'][^>]*>.*?<\/p>\s*/si',
            '/<p[^>]*>\s*See also:\s*<a\s.*?<\/p>\s*/si',
        );
        // Group B: text-only fallback (link already stripped by rollback)
        $text_only = array(
            '/\s*For further insights,?\s*see our guide on\s+[^<]{5,200}?\.\s*/si',
            '/\s*Learn more about\s+[^<]{5,200}?\.\s*/si',
            '/\s*You may also find our resource on\s+[^<]{5,200}?helpful\s*\.?\s*/si',
            '/\s*Related:\s+[^<]{5,200}?\.\s*/si',
            '/\s*See also:\s+[^<]{5,200}?\.\s*/si',
        );
        return array( 'html' => $html, 'text_only' => $text_only );
    }

    /* ═══════════════════════════════════════════════════════
     *  TOTAL COUNT — all non-English published posts
     * ═══════════════════════════════════════════════════════ */
    public static function count_targets() {
        global $wpdb;
        $t = $wpdb->prefix . 'icl_translations';
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$t}'" ) !== $t ) {
            // No WPML: fall back to meta-based count
            return (int) $wpdb->get_var(
                "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key = '_dba_anti_orphan_donor_for'"
            );
        }
        return (int) $wpdb->get_var(
            "SELECT COUNT(DISTINCT p.ID)
             FROM {$wpdb->posts} p
             INNER JOIN {$t} t ON p.ID = t.element_id AND t.element_type LIKE 'post_%'
             WHERE t.language_code != 'en' AND p.post_status = 'publish'"
        );
    }

    /* ═══════════════════════════════════════════════════════
     *  FETCH next chunk of non-English post IDs (cursor-based)
     * ═══════════════════════════════════════════════════════ */
    private static function fetch_next_ids( $last_id, $limit ) {
        global $wpdb;
        $t = $wpdb->prefix . 'icl_translations';
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$t}'" ) !== $t ) {
            // No WPML: fall back to meta-based
            return $wpdb->get_col( $wpdb->prepare(
                "SELECT DISTINCT post_id FROM {$wpdb->postmeta}
                 WHERE meta_key = '_dba_anti_orphan_donor_for' AND post_id > %d
                 ORDER BY post_id ASC LIMIT %d",
                $last_id, $limit
            ) );
        }
        return $wpdb->get_col( $wpdb->prepare(
            "SELECT DISTINCT p.ID
             FROM {$wpdb->posts} p
             INNER JOIN {$t} t ON p.ID = t.element_id AND t.element_type LIKE 'post_%'
             WHERE t.language_code != 'en' AND p.post_status = 'publish' AND p.ID > %d
             ORDER BY p.ID ASC LIMIT %d",
            $last_id, $limit
        ) );
    }

    /* ═══════════════════════════════════════════════════════
     *  BULK language fetch — one query for up to 300 posts
     * ═══════════════════════════════════════════════════════ */
    private static function bulk_get_languages( $post_ids ) {
        global $wpdb;
        $t = $wpdb->prefix . 'icl_translations';
        $map = array();
        if ( empty( $post_ids ) ) return $map;
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$t}'" ) !== $t ) return $map;

        $placeholders = implode( ',', array_fill( 0, count( $post_ids ), '%d' ) );
        $results = $wpdb->get_results( $wpdb->prepare(
            "SELECT element_id, language_code FROM {$t}
             WHERE element_id IN ({$placeholders}) AND element_type LIKE 'post_%'",
            ...$post_ids
        ) );
        foreach ( $results as $row ) $map[ (int) $row->element_id ] = $row->language_code;
        return $map;
    }

    /* ═══════════════════════════════════════════════════════
     *  STATE — persistent, survives tab close
     * ═══════════════════════════════════════════════════════ */
    private static function get_state() {
        return get_option( self::STATE_KEY, array(
            'phase' => 'idle', 'last_id' => 0, 'total' => 0,
            'scanned' => 0, 'infected' => 0, 'injections' => 0,
            'fixed' => 0, 'samples' => array(), 'started' => '',
        ) );
    }
    private static function save_state( $s ) { update_option( self::STATE_KEY, $s, false ); }
    public static function reset_state() { delete_option( self::STATE_KEY ); }
    public static function get_status() { return self::get_state(); }

    /* ═══════════════════════════════════════════════════════
     *  DIAGNOSE BATCH — time-based, cursor-based
     *  Scans ALL non-English posts for English injection patterns.
     * ═══════════════════════════════════════════════════════ */
    public static function diagnose_batch() {
        global $wpdb;
        $start = time();
        $state = self::get_state();

        if ( $state['phase'] !== 'diagnosing' ) {
            $state = array(
                'phase' => 'diagnosing', 'last_id' => 0,
                'total' => self::count_targets(),
                'scanned' => 0, 'infected' => 0, 'injections' => 0,
                'fixed' => 0, 'samples' => array(), 'started' => current_time( 'mysql' ),
            );
        }

        $ids = self::fetch_next_ids( $state['last_id'], self::FETCH_CHUNK );
        if ( empty( $ids ) ) {
            $state['phase'] = 'done';
            self::save_state( $state );
            return array( 'done' => true, 'state' => $state );
        }

        $lang_map = self::bulk_get_languages( $ids );
        $patterns = self::get_patterns();
        $batch_n  = 0;

        foreach ( $ids as $pid ) {
            if ( ( time() - $start ) >= self::TIME_LIMIT ) break;

            $lang = $lang_map[ $pid ] ?? 'en';
            // Skip English posts (shouldn't be in the set, but safety check)
            if ( $lang === 'en' ) {
                $state['scanned']++;
                $state['last_id'] = (int) $pid;
                $batch_n++;
                continue;
            }

            $post = get_post( (int) $pid );
            if ( ! $post || empty( $post->post_content ) ) {
                $state['scanned']++;
                $state['last_id'] = (int) $pid;
                $batch_n++;
                continue;
            }

            $content = $post->post_content;
            $count   = 0;

            foreach ( $patterns['html'] as $p ) $count += preg_match_all( $p, $content, $m );
            foreach ( $patterns['text_only'] as $p ) $count += preg_match_all( $p, $content, $m );

            if ( $count > 0 ) {
                $state['infected']++;
                $state['injections'] += $count;
                if ( count( $state['samples'] ) < 30 )
                    $state['samples'][] = array( 'id' => $post->ID, 'title' => mb_strimwidth( $post->post_title, 0, 55, '…' ), 'lang' => $lang, 'n' => $count );
            }

            $state['scanned']++;
            $state['last_id'] = (int) $pid;
            $batch_n++;
        }

        self::save_state( $state );
        $pct  = $state['total'] > 0 ? intval( $state['scanned'] / $state['total'] * 100 ) : 100;
        $more = ( $state['scanned'] < $state['total'] );

        return array(
            'done'  => ! $more, 'batch' => $batch_n,
            'pct'   => min( $more ? 99 : 100, $pct ),
            'state' => $state,
        );
    }

    /* ═══════════════════════════════════════════════════════
     *  FIX BATCH — time-based, cursor-based
     *  Scans ALL non-English posts. Fixes those with patterns.
     * ═══════════════════════════════════════════════════════ */
    public static function fix_batch( $dry_run = true ) {
        global $wpdb;
        $start = time();
        $phase = $dry_run ? 'dry_run' : 'live_fix';
        $state = self::get_state();

        if ( $state['phase'] !== $phase ) {
            $state = array(
                'phase' => $phase, 'last_id' => 0,
                'total' => self::count_targets(),
                'scanned' => 0, 'infected' => 0, 'injections' => 0,
                'fixed' => 0, 'samples' => array(), 'started' => current_time( 'mysql' ),
            );
        }

        $ids = self::fetch_next_ids( $state['last_id'], self::FETCH_CHUNK );
        if ( empty( $ids ) ) {
            $state['phase'] = 'done';
            self::save_state( $state );
            self::log_final( $state, $dry_run );
            if ( ! $dry_run ) self::cleanup_meta();
            return array( 'done' => true, 'state' => $state );
        }

        $lang_map = self::bulk_get_languages( $ids );
        $patterns = self::get_patterns();
        $batch_n  = 0;
        $batch_fx = 0;

        foreach ( $ids as $pid ) {
            if ( ( time() - $start ) >= self::TIME_LIMIT ) break;

            $lang = $lang_map[ $pid ] ?? 'en';
            if ( $lang === 'en' ) {
                $state['scanned']++;
                $state['last_id'] = (int) $pid;
                $batch_n++;
                continue;
            }

            $post = get_post( (int) $pid );
            if ( ! $post || empty( $post->post_content ) ) {
                $state['scanned']++;
                $state['last_id'] = (int) $pid;
                $batch_n++;
                continue;
            }

            $content  = $post->post_content;
            $original = $content;
            $changed  = false;

            // Pass 1: HTML patterns (sentence + <a> with nested tags)
            foreach ( $patterns['html'] as $p ) {
                $new = preg_replace( $p, ' ', $content );
                if ( $new !== null && $new !== $content ) { $content = $new; $changed = true; }
            }
            // Pass 2: text-only fallback
            foreach ( $patterns['text_only'] as $p ) {
                $new = preg_replace( $p, ' ', $content );
                if ( $new !== null && $new !== $content ) { $content = $new; $changed = true; }
            }

            if ( $changed ) {
                // Clean empties and excess whitespace
                $content = preg_replace( '/<p[^>]*>\s*<\/p>/', '', $content );
                $content = preg_replace( '/\s{3,}/', ' ', $content );

                if ( ! $dry_run ) {
                    self::silent_update( $post->ID, $content );
                }

                $state['fixed']++;
                $batch_fx++;

                if ( count( $state['samples'] ) < 25 ) {
                    $removed = strlen( $original ) - strlen( $content );
                    $state['samples'][] = array(
                        'id' => $post->ID,
                        'title' => mb_strimwidth( $post->post_title, 0, 45, '…' ),
                        'lang' => $lang,
                        'rem' => $removed,
                        'pct' => round( $removed / max( 1, strlen( $original ) ) * 100, 1 ),
                    );
                }
            }

            $state['scanned']++;
            $state['last_id'] = (int) $pid;
            $batch_n++;
        }

        self::save_state( $state );
        $pct  = $state['total'] > 0 ? intval( $state['scanned'] / $state['total'] * 100 ) : 100;
        $more = ( $state['scanned'] < $state['total'] );

        if ( ! $more && ! $dry_run ) self::cleanup_meta();

        return array(
            'done' => ! $more, 'batch' => $batch_n, 'batch_fixed' => $batch_fx,
            'pct'  => min( $more ? 99 : 100, $pct ), 'state' => $state,
        );
    }

    /* ── Cleanup orphaned meta after all fixes ── */
    private static function cleanup_meta() {
        global $wpdb;
        $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_dba_anti_orphan_donor_for'" );
        $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_dba_anti_orphan_injected'" );
    }

    private static function silent_update( $pid, $content ) {
        global $wpdb;
        $wpdb->update( $wpdb->posts, array( 'post_content' => $content ), array( 'ID' => $pid ), array( '%s' ), array( '%d' ) );
        clean_post_cache( $pid );
    }

    private static function log_final( $state, $dry ) {
        $log = get_option( self::LOG_KEY, array() );
        array_unshift( $log, array(
            'mode' => $dry ? 'dry_run' : 'live_fix', 'total' => $state['scanned'],
            'fixed' => $state['fixed'], 'started' => $state['started'], 'ended' => current_time( 'mysql' ),
        ) );
        update_option( self::LOG_KEY, array_slice( $log, 0, 50 ), false );
    }
    public static function get_log() { return get_option( self::LOG_KEY, array() ); }
}


/* ██████████████████████████████████████████████████████████████████████
 *  PART 2 — THIRD-PARTY LINK SCANNER
 * ██████████████████████████████████████████████████████████████████████ */

class DBA_Third_Party_Link_Scanner {

    const ALLOWED    = array( 'www.damalion.com', 'damalion.com' );
    const TIME_LIMIT = 25;
    const STATE_KEY  = 'dba_3p_state';

    public static function scan_batch( $urls, $cursor = 0 ) {
        global $wpdb;
        $start = time();
        $state = ( $cursor === 0 )
            ? array( 'scanned' => 0, 'with_ext' => 0, 'total_ext' => 0, 'domains' => array(), 'pages' => array(), 'flagged_ids' => array() )
            : get_option( self::STATE_KEY, array( 'scanned' => 0, 'with_ext' => 0, 'total_ext' => 0, 'domains' => array(), 'pages' => array(), 'flagged_ids' => array() ) );

        $total = count( $urls );
        $idx   = $cursor;

        while ( $idx < $total ) {
            if ( ( time() - $start ) >= self::TIME_LIMIT ) break;
            $url = trim( $urls[ $idx ] ); $idx++;
            if ( empty( $url ) || strtolower( $url ) === 'url' ) continue;

            $pid = url_to_postid( $url );
            if ( ! $pid ) {
                $slug = basename( rtrim( parse_url( $url, PHP_URL_PATH ) ?: '', '/' ) );
                $pid = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_name=%s AND post_status='publish' LIMIT 1", $slug ) );
            }
            if ( ! $pid ) { $state['pages'][] = array( 'url' => $url, 'error' => 'Not found' ); $state['scanned']++; continue; }

            $post = get_post( $pid );
            if ( ! $post ) { $state['scanned']++; continue; }
            $ext = self::find_external( $post->post_content );
            $state['scanned']++;
            if ( ! empty( $ext ) ) {
                $state['with_ext']++; $state['total_ext'] += count( $ext ); $state['flagged_ids'][] = $pid;
                foreach ( $ext as $l ) $state['domains'][ $l['domain'] ] = ( $state['domains'][ $l['domain'] ] ?? 0 ) + 1;
            }
            $state['pages'][] = array( 'url' => $url, 'post_id' => $pid, 'title' => $post->post_title, 'external_links' => $ext, 'edit_url' => get_edit_post_link( $pid, 'raw' ) );
        }

        $done = ( $idx >= $total );
        if ( ! $done ) update_option( self::STATE_KEY, $state, false ); else delete_option( self::STATE_KEY );
        arsort( $state['domains'] );
        return array( 'done' => $done, 'cursor' => $idx, 'total' => $total, 'pct' => min( $done ? 100 : 99, intval( $idx / max( 1, $total ) * 100 ) ), 'state' => $state );
    }

    public static function find_external( $html ) {
        $ext = array();
        if ( ! preg_match_all( '/<a\s+[^>]*href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/si', $html, $ms, PREG_SET_ORDER ) ) return $ext;
        foreach ( $ms as $m ) {
            $href = $m[1];
            if ( preg_match( '/^(#|mailto:|tel:|javascript:)/i', $href ) || ! preg_match( '/^https?:\/\//i', $href ) ) continue;
            $d = strtolower( parse_url( $href, PHP_URL_HOST ) ?: '' );
            $ok = false; foreach ( self::ALLOWED as $a ) if ( $d === $a || str_ends_with( $d, '.' . $a ) ) { $ok = true; break; }
            if ( ! $ok && $d ) $ext[] = array( 'href' => $href, 'text' => mb_strimwidth( wp_strip_all_tags( $m[2] ), 0, 80, '…' ), 'domain' => $d );
        }
        return $ext;
    }

    public static function remove_batch( $pids, $dry = true ) {
        $start = time();
        $r = array( 'total' => count( $pids ), 'cleaned' => 0, 'removed' => 0 );
        foreach ( $pids as $pid ) {
            if ( ( time() - $start ) >= 25 ) break;
            $res = self::remove_from_post( (int) $pid, $dry );
            if ( $res['count'] ) { $r['cleaned']++; $r['removed'] += $res['count']; }
        }
        return $r;
    }

    public static function remove_from_post( $pid, $dry = true ) {
        $post = get_post( $pid ); if ( ! $post ) return array( 'count' => 0 );
        $content = $post->post_content; $orig = $content; $rm = 0;
        $content = preg_replace_callback( '/<a\s+[^>]*href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/si', function ( $m ) use ( &$rm ) {
            $href = $m[1]; $d = strtolower( parse_url( $href, PHP_URL_HOST ) ?: '' );
            if ( ! preg_match( '/^https?:\/\//i', $href ) ) return $m[0];
            $ok = false; foreach ( DBA_Third_Party_Link_Scanner::ALLOWED as $a ) if ( $d === $a || str_ends_with( $d, '.' . $a ) ) { $ok = true; break; }
            if ( ! $ok && $d ) { $rm++; return $m[2]; } return $m[0];
        }, $content );
        if ( $content !== $orig && ! $dry ) {
            global $wpdb;
            $wpdb->update( $wpdb->posts, array( 'post_content' => $content ), array( 'ID' => $pid ), array( '%s' ), array( '%d' ) );
            clean_post_cache( $pid );
        }
        return array( 'count' => $rm );
    }

    public static function reset() { delete_option( self::STATE_KEY ); }
}


/* ██████████████████████████████████████████████████████████████████████
 *  PART 3 — ADMIN + AJAX
 * ██████████████████████████████████████████████████████████████████████ */

add_action( 'admin_menu', function () {
    add_submenu_page( 'dba-settings', 'Cleanup English Spam', '🧹 Cleanup Spam', 'manage_options', 'dba-ao-cleanup', 'dba_aoc_page' );
    add_submenu_page( 'dba-settings', 'Third-Party Links', '🔗 3rd-Party Links', 'manage_options', 'dba-ao-3rdparty', 'dba_3p_page' );
}, 100 );

add_action( 'wp_ajax_dba_aoc_diagnose', function () {
    check_ajax_referer( 'dba_admin_nonce', 'nonce' );
    if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Forbidden' );
    try { wp_send_json_success( DBA_Anti_Orphan_Cleanup::diagnose_batch() ); }
    catch ( \Throwable $e ) { wp_send_json_error( $e->getMessage() ); }
});
add_action( 'wp_ajax_dba_aoc_fix', function () {
    check_ajax_referer( 'dba_admin_nonce', 'nonce' );
    if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Forbidden' );
    $dry = ( $_POST['dry_run'] ?? '1' ) === '1';
    try { wp_send_json_success( DBA_Anti_Orphan_Cleanup::fix_batch( $dry ) ); }
    catch ( \Throwable $e ) { wp_send_json_error( $e->getMessage() ); }
});
add_action( 'wp_ajax_dba_aoc_status', function () {
    check_ajax_referer( 'dba_admin_nonce', 'nonce' );
    wp_send_json_success( DBA_Anti_Orphan_Cleanup::get_status() );
});
add_action( 'wp_ajax_dba_aoc_reset', function () {
    check_ajax_referer( 'dba_admin_nonce', 'nonce' );
    if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Forbidden' );
    DBA_Anti_Orphan_Cleanup::reset_state();
    wp_send_json_success( 'Reset.' );
});

add_action( 'wp_ajax_dba_3p_scan', function () {
    check_ajax_referer( 'dba_admin_nonce', 'nonce' );
    if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Forbidden' );
    $cursor = max( 0, intval( $_POST['cursor'] ?? 0 ) );
    if ( $cursor === 0 ) DBA_Third_Party_Link_Scanner::reset();
    $urls = array_values( array_filter( array_map( 'trim', explode( "\n", sanitize_textarea_field( $_POST['urls'] ?? '' ) ) ) ) );
    if ( empty( $urls ) ) wp_send_json_error( 'No URLs.' );
    try { wp_send_json_success( DBA_Third_Party_Link_Scanner::scan_batch( $urls, $cursor ) ); }
    catch ( \Throwable $e ) { wp_send_json_error( $e->getMessage() ); }
});
add_action( 'wp_ajax_dba_3p_remove', function () {
    check_ajax_referer( 'dba_admin_nonce', 'nonce' );
    if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Forbidden' );
    $dry  = ( $_POST['dry_run'] ?? '1' ) === '1';
    $pids = json_decode( stripslashes( $_POST['post_ids'] ?? '[]' ), true );
    if ( empty( $pids ) ) wp_send_json_error( 'No IDs.' );
    wp_send_json_success( DBA_Third_Party_Link_Scanner::remove_batch( $pids, $dry ) );
});


/* ══════════════════════════════════════════════════════════════
 *  PAGE 1 — CLEANUP UI
 * ══════════════════════════════════════════════════════════════ */
function dba_aoc_page() {
    $nonce = wp_create_nonce( 'dba_admin_nonce' );
    $state = DBA_Anti_Orphan_Cleanup::get_status();
    $log   = DBA_Anti_Orphan_Cleanup::get_log();
    $total = DBA_Anti_Orphan_Cleanup::count_targets();
    ?>
    <style>
    .aoc-p{background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:18px}
    .aoc-b{border-radius:8px;padding:10px 22px;font-size:14px;font-weight:700;cursor:pointer;border:2px solid;transition:all .15s;margin-right:8px}
    .aoc-b:disabled{opacity:.4;cursor:not-allowed}
    .bl{background:#1a56db;color:#fff;border-color:#1a56db}.bl:hover:not(:disabled){background:#1e40af}
    .am{background:#f59e0b;color:#fff;border-color:#f59e0b}.am:hover:not(:disabled){background:#d97706}
    .rd{background:#dc2626;color:#fff;border-color:#dc2626}.rd:hover:not(:disabled){background:#b91c1c}
    .gy{background:#fff;color:#6b7280;border-color:#d1d5db}.gy:hover:not(:disabled){background:#f3f4f6}
    .gn{background:#059669;color:#fff;border-color:#059669}.gn:hover:not(:disabled){background:#047857}
    .bar{background:#e5e7eb;border-radius:6px;height:30px;overflow:hidden;position:relative;margin:16px 0}
    .fill{height:100%;border-radius:6px;transition:width .3s;background:#1a56db}.fill.ok{background:#059669}
    .pct{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font:700 13px/1 sans-serif;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.3)}
    .st{font-size:14px;color:#666;margin:8px 0;min-height:22px}
    .stat{display:inline-block;background:#f3f4f6;border-radius:8px;padding:12px 20px;margin:6px 8px 6px 0;text-align:center}
    .stat b{display:block;font-size:26px;color:#111}.stat span{font-size:12px;color:#666}
    .tbl{width:100%;border-collapse:collapse;margin-top:14px;font-size:13px}
    .tbl th{background:#f9fafb;text-align:left;padding:8px 12px;border-bottom:2px solid #e5e7eb}
    .tbl td{padding:8px 12px;border-bottom:1px solid #f3f4f6}
    </style>

    <div class="wrap" style="max-width:1000px">
    <h1>🧹 Anti-Orphan Cleanup</h1>

    <div class="aoc-p" style="background:#f0f9ff;border-color:#93c5fd">
        <b>Scans ALL <?php echo number_format( $total ); ?> non-English posts</b> (every language: FR, NL, DE, ES, IT, PT…).<br>
        Time-based batching (~200-300 posts per 25s call). Resumable. Cloudflare-safe.<br><br>
        <b>① Diagnose</b> → counts infected posts &nbsp;|&nbsp;
        <b>② Dry Run</b> → simulates fix &nbsp;|&nbsp;
        <b>③ Live Fix</b> → removes English spam, saves
        <?php if ( $state['phase'] !== 'idle' && $state['phase'] !== 'done' ) : ?>
            <br><br><span style="color:#f59e0b;font-weight:700">⚠️ Run in progress: <?php echo esc_html( $state['phase'] ); ?> — <?php echo number_format( (int) $state['scanned'] ); ?>/<?php echo number_format( (int) $state['total'] ); ?>. Click Resume or Reset.</span>
        <?php endif; ?>
    </div>

    <div class="aoc-p">
        <button class="aoc-b bl" id="b-diag" onclick="go('diagnose')">① Diagnose</button>
        <button class="aoc-b am" id="b-dry" onclick="go('dry')">② Dry Run</button>
        <button class="aoc-b rd" id="b-live" onclick="if(confirm('Fix ALL non-English posts now?'))go('live')">③ LIVE FIX</button>
        <?php if ( $state['phase'] !== 'idle' && $state['phase'] !== 'done' ) : ?>
            <button class="aoc-b gn" id="b-resume" onclick="resume()">▶ Resume</button>
        <?php endif; ?>
        <button class="aoc-b gy" onclick="rst()" style="float:right">Reset</button>
        <div class="bar" id="bar" style="display:none"><div class="fill" id="fill" style="width:0%"></div><div class="pct" id="pct">0%</div></div>
        <div class="st" id="st"><?php if ( $total ) echo number_format( $total ) . ' non-English posts to scan (all languages).'; ?></div>
    </div>

    <div class="aoc-p" id="res" style="display:none"><h3 style="margin-top:0">Results</h3><div id="stats"></div><div id="samp"></div></div>

    <?php if ( ! empty( $log ) ) : ?>
    <div class="aoc-p"><h3 style="margin-top:0">Run History</h3>
    <table class="tbl"><tr><th>Started</th><th>Ended</th><th>Mode</th><th>Scanned</th><th>Fixed</th></tr>
    <?php foreach ( array_slice( $log, 0, 10 ) as $e ) : ?>
    <tr><td><?php echo esc_html($e['started']??'');?></td><td><?php echo esc_html($e['ended']??'');?></td>
    <td><?php echo ($e['mode']??'')==='live_fix'?'🔧 <b>LIVE</b>':'🔍 Dry';?></td>
    <td><?php echo number_format((int)($e['total']??0));?></td><td><b><?php echo number_format((int)($e['fixed']??0));?></b></td></tr>
    <?php endforeach;?></table></div>
    <?php endif; ?>
    </div>

    <script>
    const N='<?php echo esc_js($nonce);?>';
    let running=false;
    function ax(a,d,cb){d.action=a;d.nonce=N;jQuery.post(ajaxurl,d,r=>{if(r.success)cb(r.data);else{s('Error: '+(r.data||'?'),1);running=false;btn(1);}}).fail(x=>{s('⚠️ Connection lost — click Resume to continue from where it stopped.',1);running=false;btn(1);});}
    function s(m,e){const el=document.getElementById('st');el.textContent=m;el.style.color=e?'#dc2626':'#666';}
    function pb(p,c){document.getElementById('bar').style.display='block';document.getElementById('fill').style.width=p+'%';document.getElementById('fill').className='fill'+(c?' '+c:'');document.getElementById('pct').textContent=p+'%';}
    function btn(e){document.querySelectorAll('.aoc-b').forEach(b=>b.disabled=!e);}

    function go(mode){
        if(running)return;running=true;btn(0);
        ax('dba_aoc_reset',{},()=>{
            if(mode==='diagnose'){s('Diagnosing…');pb(2,'');diagLoop();}
            else if(mode==='dry'){s('Dry run starting…');pb(2,'');fixLoop(true);}
            else{s('LIVE FIX starting…');pb(2,'');fixLoop(false);}
        });
    }

    function resume(){
        if(running)return;running=true;btn(0);
        ax('dba_aoc_status',{},d=>{
            if(d.phase==='diagnosing'){s('Resuming diagnose…');diagLoop();}
            else if(d.phase==='dry_run'){s('Resuming dry run…');fixLoop(true);}
            else if(d.phase==='live_fix'){s('Resuming live fix…');fixLoop(false);}
            else{s('Nothing to resume.');running=false;btn(1);}
        });
    }

    function diagLoop(){
        ax('dba_aoc_diagnose',{},d=>{
            pb(d.pct,d.done?'ok':'');
            let st=d.state;
            s('Scanned '+st.scanned.toLocaleString()+'/'+st.total.toLocaleString()+' — '+st.infected.toLocaleString()+' infected ('+st.injections.toLocaleString()+' injections) — ~'+d.batch+' posts/batch');
            if(!d.done){setTimeout(diagLoop,300);return;}
            pb(100,'ok');showResults(st,'diag');
            s('✅ Done: '+st.infected.toLocaleString()+' infected posts with '+st.injections.toLocaleString()+' injections found across all languages.');
            running=false;btn(1);
        });
    }

    function fixLoop(dry){
        let label=dry?'DRY RUN':'LIVE FIX';
        ax('dba_aoc_fix',{dry_run:dry?'1':'0'},d=>{
            pb(d.pct,d.done?'ok':'');
            let st=d.state;
            s(label+': '+st.scanned.toLocaleString()+'/'+st.total.toLocaleString()+' processed, '+st.fixed.toLocaleString()+' '+(dry?'would be ':'')+'fixed (~'+d.batch+' posts/batch)');
            if(!d.done){setTimeout(()=>fixLoop(dry),300);return;}
            pb(100,'ok');showResults(st,dry?'dry':'live');
            s('✅ '+label+' complete: '+st.fixed.toLocaleString()+' posts '+(dry?'would be ':'')+'cleaned across all languages.');
            running=false;btn(1);
        });
    }

    function showResults(st,mode){
        document.getElementById('res').style.display='block';
        let h='<div class="stat"><b>'+st.total.toLocaleString()+'</b><span>Non-EN Posts Scanned</span></div>';
        h+='<div class="stat"><b style="color:'+(st.infected||st.fixed?'#dc2626':'#059669')+'">'+(mode==='diag'?st.infected:st.fixed).toLocaleString()+'</b><span>'+(mode==='diag'?'Infected':mode==='dry'?'Would Fix':'Fixed')+'</span></div>';
        if(st.injections)h+='<div class="stat"><b style="color:#dc2626">'+st.injections.toLocaleString()+'</b><span>Total Injections</span></div>';
        document.getElementById('stats').innerHTML=h;
        if(st.samples&&st.samples.length){
            let t='<table class="tbl"><tr><th>ID</th><th>Title</th><th>Lang</th><th>'+(mode==='diag'?'Injections':'Removed')+'</th></tr>';
            st.samples.forEach(x=>{
                t+='<tr><td>'+x.id+'</td><td>'+x.title+'</td><td><b>'+x.lang+'</b></td>';
                t+='<td style="color:#dc2626;font-weight:700">'+(x.n||('-'+x.rem+' ('+x.pct+'%)'))+'</td></tr>';
            });
            t+='</table>';document.getElementById('samp').innerHTML=t;
        }
    }

    function rst(){ax('dba_aoc_reset',{},()=>{s('Reset.');pb(0,'');document.getElementById('bar').style.display='none';document.getElementById('res').style.display='none';});}
    </script>
    <?php
}


/* ══════════════════════════════════════════════════════════════
 *  PAGE 2 — THIRD-PARTY LINK SCANNER UI
 * ══════════════════════════════════════════════════════════════ */
function dba_3p_page() {
    $nonce = wp_create_nonce( 'dba_admin_nonce' );
    $default_urls = "https://www.damalion.com/our-expertise/\nhttps://www.damalion.com/blog/\nhttps://www.damalion.com/art-signature/\nhttps://www.damalion.com/damalion-advertising-global/\nhttps://www.damalion.com/login/\nhttps://www.damalion.com/lets-connect/\nhttps://www.damalion.com/your-career/\nhttps://www.damalion.com/fund-structuring-services/\nhttps://www.damalion.com/crypto-fund-structuring-services/\nhttps://www.damalion.com/esg-funds-structuring-services/\nhttps://www.damalion.com/real-estate-fund-structuring-services/\nhttps://www.damalion.com/hedge-fund-structuring-services/\nhttps://www.damalion.com/structuring-services-for-private-debt-and-direct-lending/\nhttps://www.damalion.com/international-investors/\nhttps://www.damalion.com/corporate-finance/\nhttps://www.damalion.com/finance-solutions-of-projects-in-the-united-states-of-america\nhttps://www.damalion.com/international-real-estate-sale-and-investment-services/\nhttps://www.damalion.com/360-wine-success/\nhttps://www.damalion.com/compliance-risk/\nhttps://www.damalion.com/damalion-compliance-help-desk/\nhttps://www.damalion.com/global-advisory/\nhttps://www.damalion.com/esg-strategy-compliance-services/\nhttps://www.damalion.com/360-success-strategy/\nhttps://www.damalion.com/business-recovering-growth-focus/\nhttps://www.damalion.com/international-sales-business-development-services/\nhttps://www.damalion.com/damalion-aviation-services/\nhttps://www.damalion.com/aircraft-registration-private-jet-business-jet/\nhttps://www.damalion.com/aircraft-financing-private-jet-business-jet/\nhttps://www.damalion.com/aircraft-sales-private-jet-business-jet/\nhttps://www.damalion.com/institutions/\nhttps://www.damalion.com/family-offices/\nhttps://www.damalion.com/international-desk/\nhttps://www.damalion.com/aviation-private-corporate-private-equity/\nhttps://www.damalion.com/commercial-real-estate-private-equity/\nhttps://www.damalion.com/urban-development-private-equity/\nhttps://www.damalion.com/automotive-industry-private-equity/\nhttps://www.damalion.com/consumer-products-private-equity/\nhttps://www.damalion.com/retails-wholesale-distribution-private-equity/\nhttps://www.damalion.com/transportation-logistics-private-equity/\nhttps://www.damalion.com/traditional-renewable-energies-private-equity/\nhttps://www.damalion.com/power-utilities-private-equity/\nhttps://www.damalion.com/mining-metals-private-equity/\nhttps://www.damalion.com/sustainable-finance-private-equity/\nhttps://www.damalion.com/doing-business-in-australia/\nhttps://www.damalion.com/doing-business-in-bolivia/\nhttps://www.damalion.com/doing-business-in-brazil/\nhttps://www.damalion.com/doing-business-in-chile/\nhttps://www.damalion.com/doing-business-in-denmark/\nhttps://www.damalion.com/doing-business-in-ecuador/\nhttps://www.damalion.com/doing-business-in-el-salvador/\nhttps://www.damalion.com/doing-business-in-finland/\nhttps://www.damalion.com/doing-business-in-paraguay/\nhttps://www.damalion.com/doing-business-in-peru/\nhttps://www.damalion.com/doing-business-in-portugal/\nhttps://www.damalion.com/doing-business-in-puerto-rico/\nhttps://www.damalion.com/doing-business-in-qatar/\nhttps://www.damalion.com/doing-business-in-russia/\nhttps://www.damalion.com/doing-business-in-rwanda/\nhttps://www.damalion.com/doing-business-in-south-korea/\nhttps://www.damalion.com/doing-business-in-spain/\nhttps://www.damalion.com/doing-business-in-sweden/\nhttps://www.damalion.com/doing-business-in-switzerland/";
    ?>
    <style>
    .tp-p{background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:18px}
    .tp-b{border-radius:8px;padding:10px 22px;font-size:14px;font-weight:700;cursor:pointer;border:2px solid;transition:all .15s;margin-right:8px}
    .tp-b:disabled{opacity:.4;cursor:not-allowed}
    .bl2{background:#1a56db;color:#fff;border-color:#1a56db}.rd2{background:#dc2626;color:#fff;border-color:#dc2626}.am2{background:#f59e0b;color:#fff;border-color:#f59e0b}
    .tp-bar{background:#e5e7eb;border-radius:6px;height:24px;overflow:hidden;position:relative;margin:12px 0}
    .tp-fill{height:100%;border-radius:6px;transition:width .3s;background:#1a56db}.tp-fill.ok{background:#059669}
    .tp-pct{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font:700 12px/1 sans-serif;color:#fff}
    .stat2{display:inline-block;background:#f3f4f6;border-radius:8px;padding:12px 20px;margin:6px 8px 6px 0;text-align:center}.stat2 b{display:block;font-size:24px;color:#111}.stat2 span{font-size:12px;color:#666}
    .tbl2{width:100%;border-collapse:collapse;margin-top:14px;font-size:13px}.tbl2 th{background:#f9fafb;text-align:left;padding:8px 12px;border-bottom:2px solid #e5e7eb}.tbl2 td{padding:8px 12px;border-bottom:1px solid #f3f4f6;word-break:break-all}
    .ext{color:#dc2626;font-weight:600}
    .tp-st{font-size:14px;color:#666;margin:8px 0}
    </style>

    <div class="wrap" style="max-width:1100px">
    <h1>🔗 Third-Party Link Scanner</h1>
    <div class="tp-p"><textarea id="tp-urls" rows="8" style="width:100%;font-family:monospace;font-size:11px"><?php echo esc_textarea($default_urls);?></textarea>
    <div class="tp-bar" id="tp-bar" style="display:none"><div class="tp-fill" id="tp-fill" style="width:0%"></div><div class="tp-pct" id="tp-pct">0%</div></div>
    <br><button class="tp-b bl2" id="tp-scan" onclick="tpGo()">🔍 Scan</button>
    <button class="tp-b am2" id="tp-dry" onclick="tpRm(true)" disabled>🧪 Dry Run</button>
    <button class="tp-b rd2" id="tp-live" onclick="if(confirm('Remove?'))tpRm(false)" disabled>🗑️ Remove All</button>
    <div class="tp-st" id="tp-st"></div></div>
    <div class="tp-p" id="tp-res" style="display:none"><h3 style="margin-top:0">Results</h3><div id="tp-stats"></div><div id="tp-dom"></div><div id="tp-det"></div></div>
    </div>

    <script>
    const TN='<?php echo esc_js($nonce);?>';let tpIds=[],tpUrls='';
    function tpAx(a,d,cb){d.action=a;d.nonce=TN;jQuery.post(ajaxurl,d,r=>{if(r.success)cb(r.data);else{document.getElementById('tp-st').textContent='Error: '+(r.data||'?');document.getElementById('tp-scan').disabled=false;}}).fail(x=>{document.getElementById('tp-st').textContent='Connection lost.';document.getElementById('tp-scan').disabled=false;});}
    function tpPb(p,c){document.getElementById('tp-bar').style.display='block';document.getElementById('tp-fill').style.width=p+'%';document.getElementById('tp-fill').className='tp-fill'+(c?' '+c:'');document.getElementById('tp-pct').textContent=p+'%';}

    function tpGo(){document.getElementById('tp-scan').disabled=true;tpUrls=document.getElementById('tp-urls').value;document.getElementById('tp-st').textContent='Scanning…';tpPb(5,'');tpLoop(0);}
    function tpLoop(cur){
        tpAx('dba_3p_scan',{urls:tpUrls,cursor:cur},d=>{
            tpPb(d.pct,d.done?'ok':'');
            document.getElementById('tp-st').textContent='Scanned '+d.cursor+'/'+d.total+'…';
            if(!d.done){setTimeout(()=>tpLoop(d.cursor),300);return;}
            document.getElementById('tp-scan').disabled=false;
            document.getElementById('tp-res').style.display='block';
            let st=d.state;tpIds=st.flagged_ids||[];
            let h='<div class="stat2"><b>'+st.scanned+'</b><span>Scanned</span></div><div class="stat2"><b style="color:'+(st.with_ext?'#dc2626':'#059669')+'">'+st.with_ext+'</b><span>With External</span></div><div class="stat2"><b style="color:#dc2626">'+st.total_ext+'</b><span>External Links</span></div>';
            document.getElementById('tp-stats').innerHTML=h;
            if(st.domains&&Object.keys(st.domains).length){let dt='<h4>Domains:</h4><table class="tbl2"><tr><th>Domain</th><th>Count</th></tr>';for(let dm in st.domains)dt+='<tr><td class="ext">'+dm+'</td><td>'+st.domains[dm]+'</td></tr>';dt+='</table>';document.getElementById('tp-dom').innerHTML=dt;}
            let t='<h4>Pages:</h4><table class="tbl2"><tr><th>Page</th><th>External Links</th><th>Edit</th></tr>';
            st.pages.forEach(pg=>{let ec=(pg.external_links||[]).length;t+='<tr><td><b>'+(pg.title||'—')+'</b><br><small style="color:#999">'+(pg.url||'').substring(0,70)+'</small></td><td>';if(pg.error)t+=pg.error;else if(!ec)t+='✅ Clean';else(pg.external_links||[]).forEach(l=>{t+='<span class="ext">'+l.domain+'</span>: '+l.text+'<br>';});t+='</td><td>';if(ec&&pg.edit_url)t+='<a href="'+pg.edit_url+'" target="_blank">Edit</a>';t+='</td></tr>';});
            t+='</table>';document.getElementById('tp-det').innerHTML=t;
            if(tpIds.length){document.getElementById('tp-dry').disabled=false;document.getElementById('tp-live').disabled=false;}
            document.getElementById('tp-st').textContent=st.total_ext?st.total_ext+' external links in '+st.with_ext+' pages.':'✅ All clean!';
        });
    }
    function tpRm(dry){
        document.getElementById('tp-st').textContent=(dry?'Dry run':'Removing')+'…';
        tpAx('dba_3p_remove',{dry_run:dry?'1':'0',post_ids:JSON.stringify(tpIds)},d=>{
            document.getElementById('tp-st').textContent='✅ '+(dry?'DRY RUN':'REMOVED')+': '+d.removed+' links from '+d.cleaned+' pages.'+(dry?' Click red to apply.':'');
        });
    }
    </script>
    <?php
}
