<?php

/**
 * Products functionality for Wuclean plugin.
 *
 * @package Wuclean
 */
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
// Enqueue JavaScript for products page.
add_action( 'admin_enqueue_scripts', function ( $hook ) {
    if ( is_string( $hook ) && strpos( $hook, 'wuclean-products' ) !== false ) {
        wp_enqueue_script(
            'pt-wuclean-common',
            plugins_url( 'assets/js/common.js', __FILE__ ),
            array('jquery'),
            '1.0.0',
            true
        );
        wp_enqueue_script(
            'pt-wuclean-products',
            plugins_url( 'assets/js/products.js', __FILE__ ),
            array('jquery', 'pt-wuclean-common'),
            '1.0.0',
            true
        );
        // Localize script with necessary variables.
        wp_localize_script( 'pt-wuclean-common', 'pt_wuclean_common_vars', pt_wuclean_get_common_script_strings() );
        // Base variables.
        $products_vars = array(
            'product_search_nonce'    => wp_create_nonce( 'pt_wuclean_product_search' ),
            'orphaned_search_nonce'   => wp_create_nonce( 'pt_wuclean_orphaned_search' ),
            'unused_attributes_nonce' => wp_create_nonce( 'pt_wuclean_unused_attributes_search' ),
            'product_issues_url'      => esc_url( add_query_arg( array(
                'page'     => 'wuclean-products',
                'tab'      => 'issues',
                '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
            ), admin_url( 'admin.php' ) ) ),
            'orphaned_data_url'       => esc_url( add_query_arg( array(
                'page'     => 'wuclean-products',
                'tab'      => 'orphaned_data',
                '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
            ), admin_url( 'admin.php' ) ) ),
            'unused_attributes_url'   => esc_url( add_query_arg( array(
                'page'     => 'wuclean-products',
                'tab'      => 'unused_attributes',
                '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
            ), admin_url( 'admin.php' ) ) ),
            'recheck_checking_text'   => __( 'Checking...', 'wuclean-database-cleaner-for-woocommerce' ),
            'recheck_updated_text'    => __( 'Updated', 'wuclean-database-cleaner-for-woocommerce' ),
            'recheck_error_label'     => __( 'Error:', 'wuclean-database-cleaner-for-woocommerce' ),
            'recheck_unknown_error'   => __( 'Unknown error', 'wuclean-database-cleaner-for-woocommerce' ),
            'recheck_failed_generic'  => __( 'Failed to recheck product. Please try again.', 'wuclean-database-cleaner-for-woocommerce' ),
        );
        wp_localize_script( 'pt-wuclean-products', 'pt_wuclean_products_vars', array_merge( $products_vars, pt_wuclean_get_products_script_strings() ) );
    }
} );
/**
 * Returns localized strings for the products script.
 *
 * @return array<string, string>
 */
function pt_wuclean_get_products_script_strings() {
    return array(
        'search_completed_product_issues' => __( 'Search completed. Redirecting to Product Issues tab...', 'wuclean-database-cleaner-for-woocommerce' ),
        'search_completed_orphaned'       => __( 'Search completed. Redirecting to Orphaned Data tab...', 'wuclean-database-cleaner-for-woocommerce' ),
        'search_completed_unused'         => __( 'Search completed. Redirecting to Unused Attributes tab...', 'wuclean-database-cleaner-for-woocommerce' ),
        'unknown_error'                   => __( 'Unknown error', 'wuclean-database-cleaner-for-woocommerce' ),
    );
}

/**
 * Products page with Settings, Product Issues, and Orphaned Data tabs.
 */
function pt_wuclean_render_products_page() {
    // Defense-in-depth: the admin menu is capability-gated, but verify again before reading request input.
    if ( !current_user_can( pt_wuclean_get_required_capability() ) ) {
        wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wuclean-database-cleaner-for-woocommerce' ) );
    }
    // Tab query arg: settings is the default; other tabs require a valid tab nonce (see nav links).
    $current_tab = 'settings';
    $valid_tabs = array(
        'settings',
        'issues',
        'orphaned_data',
        'unused_attributes'
    );
    if ( isset( $_GET['tab'] ) ) {
        $tab_value = sanitize_text_field( wp_unslash( $_GET['tab'] ) );
        if ( !in_array( $tab_value, $valid_tabs, true ) ) {
            $current_tab = 'settings';
        } elseif ( 'settings' === $tab_value ) {
            $current_tab = 'settings';
        } elseif ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'wuclean-products-tab' ) ) {
            $current_tab = $tab_value;
        }
    }
    echo '<div class="wrap">';
    echo '<h1>📦 ' . esc_html__( 'Products', 'wuclean-database-cleaner-for-woocommerce' ) . '</h1>';
    // Tab navigation.
    echo '<nav class="nav-tab-wrapper">';
    echo '<a href="' . esc_url( add_query_arg( array(
        'page'     => 'wuclean-products',
        'tab'      => 'settings',
        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
    ), admin_url( 'admin.php' ) ) ) . '" class="nav-tab' . (( 'settings' === $current_tab ? ' nav-tab-active' : '' )) . '">' . esc_html__( 'Settings', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
    echo '<a href="' . esc_url( add_query_arg( array(
        'page'     => 'wuclean-products',
        'tab'      => 'issues',
        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
    ), admin_url( 'admin.php' ) ) ) . '" class="nav-tab' . (( 'issues' === $current_tab ? ' nav-tab-active' : '' )) . '">' . esc_html__( 'Product Issues', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
    echo '<a href="' . esc_url( add_query_arg( array(
        'page'     => 'wuclean-products',
        'tab'      => 'orphaned_data',
        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
    ), admin_url( 'admin.php' ) ) ) . '" class="nav-tab' . (( 'orphaned_data' === $current_tab ? ' nav-tab-active' : '' )) . '">' . esc_html__( 'Product Orphan Data', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
    echo '<a href="' . esc_url( add_query_arg( array(
        'page'     => 'wuclean-products',
        'tab'      => 'unused_attributes',
        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
    ), admin_url( 'admin.php' ) ) ) . '" class="nav-tab' . (( 'unused_attributes' === $current_tab ? ' nav-tab-active' : '' )) . '">' . esc_html__( 'Unused Global Attributes', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
    echo '</nav>';
    // Standard inner wrapper for all tabs (max width, no extra padding so tables align).
    echo '<div class="pt-wuclean-page-inner" style="max-width: 1600px; box-sizing: border-box;">';
    if ( 'settings' === $current_tab ) {
        pt_wuclean_render_products_settings_tab();
    } elseif ( 'issues' === $current_tab ) {
        pt_wuclean_render_product_issues_tab();
    } elseif ( 'orphaned_data' === $current_tab ) {
        pt_wuclean_render_orphaned_data_tab();
    } elseif ( 'unused_attributes' === $current_tab ) {
        pt_wuclean_render_unused_attributes_tab();
    }
    echo '</div>';
    // Close inner wrapper.
    echo '</div>';
}

/**
 * Products Settings Tab.
 */
function pt_wuclean_render_products_settings_tab() {
    pt_wuclean_check_permissions();
    echo '<div style="margin-top:2em; max-width: 1600px;">';
    echo '<h2>⚙️ ' . esc_html__( 'Product Scan Settings', 'wuclean-database-cleaner-for-woocommerce' ) . '</h2>';
    // Info box.
    echo '<div style="max-width: 1600px;background: #e7f3ff; border: 1px solid #2196f3; border-radius: 4px; padding: 15px; margin: 20px 0;">';
    echo '<p style="margin: 0 0 10px 0;"><strong>ℹ️ ' . esc_html__( 'About Product Scans:', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong> ' . esc_html__( 'This scanner detects three types of product related issues:', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
    echo '<ul style="margin: 0; padding-left: 20px;">';
    echo '<li><strong>🔍 ' . esc_html__( 'Product Issues', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong> - ' . esc_html__( 'Missing product data such as descriptions, prices, images and more', 'wuclean-database-cleaner-for-woocommerce' ) . '</li>';
    echo '<li><strong>🗑️ ' . esc_html__( 'Product Orphan Data', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong> - ' . esc_html__( 'Product meta and variations with missing parents', 'wuclean-database-cleaner-for-woocommerce' ) . '</li>';
    echo '<li><strong>🏷️ ' . esc_html__( 'Unused Attributes', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong> - ' . esc_html__( 'Global attributes not used by any products', 'wuclean-database-cleaner-for-woocommerce' ) . '</li>';
    echo '</ul>';
    echo '</div>';
    // Manual scans section.
    echo '<div style="background: #fff; border: 1px solid #ccd0d4; border-radius: 4px; padding: 20px; margin: 20px 0; max-width: 1600px;">';
    echo '<h3 style="margin-top: 0;">🔍 ' . esc_html__( 'Manual Product Scans', 'wuclean-database-cleaner-for-woocommerce' ) . '</h3>';
    echo '<p>' . esc_html__( 'Use these buttons to manually search for product issues, orphaned data, and unused global attributes.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
    // Product Issues Search.
    echo '<div style="max-width: 1600px;margin-bottom:1.5em;">';
    echo '<h4 style="margin-bottom:0.5em;">📦 ' . esc_html__( 'Product Issues Search', 'wuclean-database-cleaner-for-woocommerce' ) . '</h4>';
    echo '<p style="margin:0 0 10px 0; color:#666; font-size:13px;">' . esc_html__( 'This search will evaluate every product in the store for issues and display findings on the Product Issues tab.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
    echo '<button type="button" id="pt-start-product-search" class="button button-primary" style="background: #2196f3; border-color: #2196f3;">🔍 ' . esc_html__( 'Search for Product Issues', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
    echo '<button type="button" id="pt-stop-product-search" class="button" style="display:none; margin-left:10px; background:#dc3232; border-color:#dc3232; color:#fff;">⏹️ ' . esc_html__( 'Stop Search', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
    echo '<div id="pt-search-progress" style="display:none; margin-top:15px;">';
    echo '<div style="background:#f0f0f0; border:1px solid #ddd; border-radius:4px; padding:15px;">';
    echo '<div style="margin-bottom:8px;"><strong>' . esc_html__( 'Search Progress:', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong></div>';
    echo '<div style="background:#fff; border:1px solid #ddd; border-radius:2px; height:25px; position:relative;">';
    echo '<div id="pt-progress-bar" style="background:#2196f3; height:100%; width:0%; transition:width 0.3s ease; border-radius:2px;"></div>';
    echo '</div>';
    echo '<div id="pt-progress-text" style="margin-top:8px; font-size:13px; color:#666;"></div>';
    echo '</div>';
    echo '</div>';
    echo '</div>';
    // Orphaned Data Search.
    echo '<div style="max-width: 1600px;margin-bottom:1.5em;">';
    echo '<h4 style="margin-bottom:0.5em;">🗑️ ' . esc_html__( 'Product Orphan Data Search', 'wuclean-database-cleaner-for-woocommerce' ) . '</h4>';
    echo '<p style="margin:0 0 10px 0; color:#666; font-size:13px;">' . esc_html__( 'Search the database for orphaned product data and display findings on the Product Orphan Data tab.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
    echo '<button type="button" id="pt-start-orphaned-search" class="button button-primary" style="background: #2196f3; border-color: #2196f3;">🔍 ' . esc_html__( 'Search for Product Orphan Data', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
    echo '<button type="button" id="pt-stop-orphaned-search" class="button" style="display:none; margin-left:10px; background:#dc3232; border-color:#dc3232; color:#fff;">⏹️ ' . esc_html__( 'Stop Search', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
    echo '<div id="pt-orphaned-search-progress" style="display:none; margin-top:15px; max-width:100%; overflow:hidden;">';
    echo '<div style="background:#f0f0f0; border:1px solid #ddd; border-radius:4px; padding:15px; max-width:100%;">';
    echo '<div style="margin-bottom:8px;"><strong>' . esc_html__( 'Search Progress:', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong></div>';
    echo '<div style="background:#fff; border:1px solid #ddd; border-radius:2px; height:25px; position:relative; max-width:100%; overflow:hidden;">';
    echo '<div id="pt-orphaned-progress-bar" style="background:#2196f3; height:100%; width:0%; transition:width 0.3s ease; border-radius:2px; max-width:100%;"></div>';
    echo '</div>';
    echo '<div id="pt-orphaned-progress-text" style="margin-top:8px; font-size:13px; color:#666; word-wrap:break-word; max-width:100%;"></div>';
    echo '</div>';
    echo '</div>';
    echo '</div>';
    // Global Attributes Search.
    echo '<div style="max-width: 1600px;margin-bottom:0;">';
    echo '<h4 style="margin-bottom:0.5em;">🏷️ ' . esc_html__( 'Global Attributes Search', 'wuclean-database-cleaner-for-woocommerce' ) . '</h4>';
    echo '<p style="margin:0 0 10px 0; color:#666; font-size:13px;">' . esc_html__( 'Search for global unused product attributes.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
    echo '<button type="button" id="pt-start-unused-attributes-search" class="button button-primary" style="background: #2196f3; border-color: #2196f3;">🔍 ' . esc_html__( 'Search for Unused Global Attributes', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
    echo '<button type="button" id="pt-stop-unused-attributes-search" class="button" style="display:none; margin-left:10px; background:#dc3232; border-color:#dc3232; color:#fff;">⏹️ ' . esc_html__( 'Stop Search', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
    echo '<div id="pt-unused-attributes-search-progress" style="display:none; margin-top:15px; max-width:100%; overflow:hidden;">';
    echo '<div style="background:#f0f0f0; border:1px solid #ddd; border-radius:4px; padding:15px; max-width:100%;">';
    echo '<div style="margin-bottom:8px;"><strong>' . esc_html__( 'Search Progress:', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong></div>';
    echo '<div style="background:#fff; border:1px solid #ddd; border-radius:2px; height:25px; position:relative; max-width:100%; overflow:hidden;">';
    echo '<div id="pt-unused-attributes-progress-bar" style="background:#2196f3; height:100%; width:0%; transition:width 0.3s ease; border-radius:2px; max-width:100%;"></div>';
    echo '</div>';
    echo '<div id="pt-unused-attributes-progress-text" style="margin-top:8px; font-size:13px; color:#666; word-wrap:break-word; max-width:100%;"></div>';
    echo '</div>';
    echo '</div>';
    echo '</div>';
    echo '</div>';
    // Close manual scans card.
    // Free version: progressive disclosure for automation benefits (Settings tab only).
    $pro_benefits = array(
        __( 'Schedule automatic scans daily, weekly or monthly and get notified by email when items are detected', 'wuclean-database-cleaner-for-woocommerce' ),
        __( 'Automatically detect and delete orphaned product data', 'wuclean-database-cleaner-for-woocommerce' ),
        __( 'Automatically detect and delete unused global product attributes', 'wuclean-database-cleaner-for-woocommerce' ),
        __( 'Automatically detect and delete trashed products after 24 hours', 'wuclean-database-cleaner-for-woocommerce' )
    );
    pt_wuclean_render_pro_automation_callout( $pro_benefits, 'products-settings' );
    echo '</div>';
    // Close main wrapper.
}

/**
 * Product Issues Tab
 */
function pt_wuclean_render_product_issues_tab() {
    pt_wuclean_check_permissions();
    // Handle clear product issues list.
    if ( isset( $_POST['clear_product_issues'] ) && isset( $_POST['pt_wuclean_product_issues_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pt_wuclean_product_issues_nonce'] ) ), 'pt_wuclean_product_issues' ) ) {
        // Clear the product issues list.
        delete_option( 'pt_wuclean_product_issues' );
        delete_transient( 'pt_wuclean_product_issues' );
        // ALWAYS reset scan timestamps and tracking to start completely fresh.
        // This ensures the next scan will treat all products as "never scanned".
        pt_wuclean_reset_product_scan_timestamps();
        // Clear option caches (avoid flushing the entire site object cache).
        wp_cache_delete( 'pt_wuclean_product_issues', 'options' );
        wp_cache_delete( 'pt_wuclean_product_scan_timestamps', 'options' );
        wp_cache_delete( 'pt_wuclean_last_scan_check', 'options' );
        wp_cache_delete( 'pt_wuclean_scan_behind_info', 'options' );
        // Redirect to show success message.
        if ( !headers_sent() ) {
            wp_safe_redirect( add_query_arg( 'cleared', '1', remove_query_arg( array('cleared', 'trashed') ) ) );
            exit;
        } else {
            wp_enqueue_script( 'jquery' );
            $redirect_url = add_query_arg( array(
                'page'     => 'wuclean-products',
                'tab'      => 'issues',
                'cleared'  => '1',
                '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
            ), admin_url( 'admin.php' ) );
            $redirect_script = 'window.location.href = ' . wp_json_encode( $redirect_url ) . ';';
            wp_add_inline_script( 'jquery', $redirect_script );
        }
    }
    // Show success message if list was cleared.
    if ( isset( $_GET['cleared'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['cleared'] ) ) ) {
        echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Product issues list has been cleared successfully. The next scan will start fresh.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p></div>';
    }
    // Get product issues - try transient first, then option as fallback.
    $product_issues = get_transient( 'pt_wuclean_product_issues' );
    $product_issues = ( $product_issues ? $product_issues : array() );
    // If transient is empty, try to get from option.
    if ( empty( $product_issues ) ) {
        $product_issues = get_option( 'pt_wuclean_product_issues', array() );
    }
    // Search functionality - define early to avoid undefined variable.
    $search_term = ( isset( $_GET['search'] ) ? sanitize_text_field( wp_unslash( $_GET['search'] ) ) : '' );
    // Apply search filtering.
    $filtered_issues = $product_issues;
    if ( !empty( $search_term ) ) {
        $search_term_str = (string) $search_term;
        $filtered_issues = array_filter( $product_issues, function ( $issue ) use($search_term_str) {
            // Search across all fields.
            $searchable_text = implode( ' ', array($issue['id'] ?? '', $issue['title'] ?? '', $issue['type'] ?? '') );
            return stripos( $searchable_text, $search_term_str ) !== false;
        } );
    }
    // Ensure unique results by product ID.
    $unique_issues = array();
    $seen_ids = array();
    foreach ( $filtered_issues as $issue ) {
        if ( !in_array( $issue['id'], $seen_ids, true ) ) {
            $unique_issues[] = $issue;
            $seen_ids[] = $issue['id'];
        }
    }
    $filtered_issues = $unique_issues;
    echo '<div style="margin-top:2em;">';
    echo '<h2>🔍 ' . esc_html__( 'Product Issues', 'wuclean-database-cleaner-for-woocommerce' ) . '</h2>';
    // Warning box.
    echo '<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin: 20px 0;">';
    echo '<p style="margin: 0;"><strong>⚠️ ' . esc_html__( 'Important Note:', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong> ' . esc_html__( 'Various tests are conducted to identify products with missing data, but this is not foolproof. Use the report below to identify products that should be updated via WooCommerce. Corrected issues can be removed from the list immediately by using the Recheck button.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
    echo '</div>';
    // Search form - Always show this so users can search/clear search.
    if ( !empty( $product_issues ) ) {
        echo '<form method="get" action="' . esc_url( admin_url( 'admin.php' ) ) . '" style="margin-bottom: 20px;">';
        echo '<input type="hidden" name="page" value="wuclean-products">';
        echo '<input type="hidden" name="tab" value="issues">';
        echo '<input type="hidden" name="_wpnonce" value="' . esc_attr( wp_create_nonce( 'wuclean-products-tab' ) ) . '">';
        $search_placeholder = esc_attr__( 'Search by Product ID, Title, or Type', 'wuclean-database-cleaner-for-woocommerce' );
        echo '<input type="text" name="search" value="' . esc_attr( $search_term ) . '" placeholder="' . esc_attr( $search_placeholder ) . '" style="width: 400px; padding: 5px; height: 30px; box-sizing: border-box;">';
        echo '<input type="submit" value="' . esc_attr__( 'Search', 'wuclean-database-cleaner-for-woocommerce' ) . '" class="button">';
        if ( !empty( $search_term ) ) {
            echo '<a href="' . esc_url( add_query_arg( array(
                'page'     => 'wuclean-products',
                'tab'      => 'issues',
                '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
            ), admin_url( 'admin.php' ) ) ) . '" class="button">' . esc_html__( 'Clear Search', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
        }
        echo '</form>';
    }
    // Check if we have any results to display.
    if ( empty( $filtered_issues ) ) {
        if ( !empty( $search_term ) ) {
            // translators: %s: search term entered by user.
            echo '<p>' . sprintf( esc_html__( 'No product issues found matching "%s".', 'wuclean-database-cleaner-for-woocommerce' ), esc_html( $search_term ) ) . '</p>';
        } else {
            echo '<p>' . esc_html__( 'No product issues are currently listed. Any new issues identified by the cleanup routine will be listed here in future.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
        }
        echo '</div>';
        // Close the main div.
        return;
    }
    echo '<form method="post" id="product-issues-form">';
    wp_nonce_field( 'pt_wuclean_product_issues', 'pt_wuclean_product_issues_nonce' );
    echo '<input type="hidden" name="action" value="pt_export_product_issues">';
    // Action buttons.
    if ( !empty( $filtered_issues ) ) {
        echo '<div style="margin-bottom: 20px;">';
        echo '<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">';
        echo '<button type="submit" name="export_products" value="1" class="button button-primary" formaction="' . esc_url( admin_url( 'admin.php?action=pt_export_product_issues' ) ) . '">' . esc_html__( 'Export List', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
        echo '<button type="submit" name="clear_product_issues" value="1" class="button" style="background: #f0ad4e; border-color: #eea236; color: #fff;" data-confirm-message="' . esc_attr( __( 'Are you sure you want to clear the entire product issues list? This will remove all current issues and the next scan will start fresh. This action cannot be undone.', 'wuclean-database-cleaner-for-woocommerce' ) ) . '">' . esc_html__( 'Clear List', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
        echo '</div>';
        echo '</div>';
    }
    echo '<table class="widefat">';
    // Sorting functionality - only trust sort/order when nonce is valid.
    $allowed_sort_columns = array(
        'id',
        'title',
        'category',
        'type',
        'price',
        'image',
        'stock',
        'variants',
        'parent',
        'sku',
        'description'
    );
    $sort_column = 'id';
    $sort_order = 'asc';
    $products_nonce_valid = false;
    if ( isset( $_GET['_wpnonce'] ) ) {
        $products_nonce_valid = wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'wuclean-products-tab' ) || wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'wuclean-products-search' );
    }
    if ( $products_nonce_valid ) {
        if ( isset( $_GET['sort'] ) ) {
            $requested_sort = sanitize_text_field( wp_unslash( $_GET['sort'] ) );
            if ( in_array( $requested_sort, $allowed_sort_columns, true ) ) {
                $sort_column = $requested_sort;
            }
        }
        if ( isset( $_GET['order'] ) ) {
            $requested_order = sanitize_text_field( wp_unslash( $_GET['order'] ) );
            if ( in_array( $requested_order, array('asc', 'desc'), true ) ) {
                $sort_order = $requested_order;
            }
        }
    }
    // Sort the filtered issues.
    if ( !empty( $filtered_issues ) ) {
        usort( $filtered_issues, function ( $a, $b ) use($sort_column, $sort_order) {
            $result = 0;
            switch ( $sort_column ) {
                case 'id':
                    $result = $a['id'] <=> $b['id'];
                    break;
                case 'title':
                    $result = strcasecmp( $a['title'], $b['title'] );
                    break;
                case 'category':
                    $result = (( $a['missing_category'] ? 1 : 0 )) <=> (( $b['missing_category'] ? 1 : 0 ));
                    break;
                case 'type':
                    $result = (( $a['missing_type'] ? 1 : 0 )) <=> (( $b['missing_type'] ? 1 : 0 ));
                    break;
                case 'price':
                    $result = (( $a['missing_price'] ? 1 : 0 )) <=> (( $b['missing_price'] ? 1 : 0 ));
                    break;
                case 'image':
                    $result = (( $a['missing_image'] ? 1 : 0 )) <=> (( $b['missing_image'] ? 1 : 0 ));
                    break;
                case 'stock':
                    $result = (( $a['missing_stock'] ? 1 : 0 )) <=> (( $b['missing_stock'] ? 1 : 0 ));
                    break;
                case 'variants':
                    $result = (( $a['missing_variants'] ? 1 : 0 )) <=> (( $b['missing_variants'] ? 1 : 0 ));
                    break;
                case 'parent':
                    $result = (( $a['missing_parent'] ? 1 : 0 )) <=> (( $b['missing_parent'] ? 1 : 0 ));
                    break;
                case 'sku':
                    $result = (( $a['missing_sku'] ? 1 : 0 )) <=> (( $b['missing_sku'] ? 1 : 0 ));
                    break;
                case 'description':
                    $result = (( $a['missing_description'] ? 1 : 0 )) <=> (( $b['missing_description'] ? 1 : 0 ));
                    break;
                default:
                    $result = $a['id'] <=> $b['id'];
            }
            return ( 'desc' === $sort_order ? -$result : $result );
        } );
    }
    // Build sort URL helper function.
    $build_sort_url = function ( $column ) use($sort_column, $sort_order, $search_term) {
        $new_order = ( $column === $sort_column && 'asc' === $sort_order ? 'desc' : 'asc' );
        $args = array(
            'sort'     => $column,
            'order'    => $new_order,
            '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
        );
        if ( !empty( $search_term ) ) {
            $args['search'] = $search_term;
        }
        return add_query_arg( $args );
    };
    // Get sort indicator.
    $get_sort_indicator = function ( $column ) use($sort_column, $sort_order) {
        if ( $column === $sort_column ) {
            return ( 'asc' === $sort_order ? ' ↑' : ' ↓' );
        }
        return '';
    };
    echo '<thead><tr>';
    echo '<th style="width: 50px; text-align: left;" title="' . esc_attr__( 'Product ID - Click to edit in WooCommerce', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'id' ) ) . '">' . esc_html__( 'ID', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'id' ) ) . '</a></th>';
    echo '<th style="text-align: left;" title="' . esc_attr__( 'Product title if available', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'title' ) ) . '">' . esc_html__( 'Title', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'title' ) ) . '</a></th>';
    echo '<th style="text-align: center;" title="' . esc_attr__( 'Missing product category', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'category' ) ) . '">' . esc_html__( 'Category', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'category' ) ) . '</a></th>';
    echo '<th style="text-align: center;" title="' . esc_attr__( 'Missing _product_type', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'type' ) ) . '">' . esc_html__( 'Type', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'type' ) ) . '</a></th>';
    echo '<th style="text-align: center;" title="' . esc_attr__( 'Missing price or regular price', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'price' ) ) . '">' . esc_html__( 'Price', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'price' ) ) . '</a></th>';
    echo '<th style="text-align: center;" title="' . esc_attr__( 'Missing product image or broken image URL', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'image' ) ) . '">' . esc_html__( 'Image', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'image' ) ) . '</a></th>';
    echo '<th style="text-align: center;" title="' . esc_attr__( 'Missing stock for a physical product', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'stock' ) ) . '">' . esc_html__( 'Stock', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'stock' ) ) . '</a></th>';
    echo '<th style="text-align: center;" title="' . esc_attr__( 'Missing variants for a variable product', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'variants' ) ) . '">' . esc_html__( 'Variants', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'variants' ) ) . '</a></th>';
    echo '<th style="text-align: center;" title="' . esc_attr__( 'Missing parent for variant product', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'parent' ) ) . '">' . esc_html__( 'Parent', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'parent' ) ) . '</a></th>';
    echo '<th style="text-align: center;" title="' . esc_attr__( 'Missing SKU', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'sku' ) ) . '">' . esc_html__( 'SKU', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'sku' ) ) . '</a></th>';
    echo '<th style="text-align: center;" title="' . esc_attr__( 'Missing long and short description', 'wuclean-database-cleaner-for-woocommerce' ) . '"><a href="' . esc_url( $build_sort_url( 'description' ) ) . '">' . esc_html__( 'Description', 'wuclean-database-cleaner-for-woocommerce' ) . wp_kses_post( $get_sort_indicator( 'description' ) ) . '</a></th>';
    echo '<th style="width: 100px;">' . esc_html__( 'Actions', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '</tr></thead><tbody>';
    // Pagination.
    // Pagination setup.
    $items_per_page = 25;
    // Standard pagination size for consistency.
    $current_page = ( isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1 );
    $total_items = count( $filtered_issues );
    $total_pages = ceil( $total_items / $items_per_page );
    $offset = ($current_page - 1) * $items_per_page;
    // Paginate the filtered results.
    $paginated_issues = array_slice( $filtered_issues, $offset, $items_per_page );
    // Pagination info.
    if ( $total_items > 0 ) {
        $pagination_start = $offset + 1;
        $pagination_end = min( $offset + $items_per_page, $total_items );
        /* translators: %1$d: start number, %2$d: end number, %3$d: total count. */
        $template = esc_html__( 'Showing %1$d-%2$d of %3$d items', 'wuclean-database-cleaner-for-woocommerce' );
        echo '<div style="margin-bottom: 10px;">';
        echo '<span class="pt-wuclean-pagination" data-template="' . esc_attr( $template ) . '" data-start="' . esc_attr( $pagination_start ) . '" data-end="' . esc_attr( $pagination_end ) . '" data-total="' . esc_attr( $total_items ) . '">' . esc_html( sprintf(
            $template,
            $pagination_start,
            $pagination_end,
            $total_items
        ) ) . '</span>';
        echo '</div>';
    }
    foreach ( $paginated_issues as $index => $issue ) {
        // Improved alternating row colors with hover effect.
        $bg_color = ( 0 === $index % 2 ? '#ffffff' : '#f9f9f9' );
        echo '<tr style="background-color: ' . esc_attr( $bg_color ) . '; transition: background-color 0.2s;" onmouseover="this.style.backgroundColor=\'#e8f4f8\'" onmouseout="this.style.backgroundColor=\'' . esc_attr( $bg_color ) . '\'">';
        ++$index;
        // Only hyperlink main products, not variants.
        if ( 'product' === $issue['type'] ) {
            echo '<td style="text-align: right;"><a href="' . esc_url( admin_url( 'post.php?post=' . $issue['id'] . '&action=edit' ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $issue['id'] ) . '</a></td>';
        } else {
            echo '<td style="text-align: right;">' . esc_html( $issue['id'] ) . '</td>';
        }
        echo '<td style="text-align: left;">' . esc_html( $issue['title'] ) . '</td>';
        echo '<td style="text-align: center;">' . (( $issue['missing_category'] ? 'X' : '' )) . '</td>';
        echo '<td style="text-align: center;">' . (( $issue['missing_type'] ? 'X' : '' )) . '</td>';
        echo '<td style="text-align: center;">' . (( $issue['missing_price'] ? 'X' : '' )) . '</td>';
        echo '<td style="text-align: center;">' . (( $issue['missing_image'] ? 'X' : '' )) . '</td>';
        echo '<td style="text-align: center;">' . (( $issue['missing_stock'] ? 'X' : '' )) . '</td>';
        echo '<td style="text-align: center;">' . (( $issue['missing_variants'] ? 'X' : '' )) . '</td>';
        echo '<td style="text-align: center;">' . (( $issue['missing_parent'] ? 'X' : '' )) . '</td>';
        echo '<td style="text-align: center;">' . (( $issue['missing_sku'] ? 'X' : '' )) . '</td>';
        echo '<td style="text-align: center;">' . (( $issue['missing_description'] ? 'X' : '' )) . '</td>';
        echo '<td style="text-align: center;">';
        echo '<button type="button" class="button button-small pt-wuclean-recheck-product" data-product-id="' . esc_attr( $issue['id'] ) . '" title="' . esc_attr__( 'Recheck this product for issues', 'wuclean-database-cleaner-for-woocommerce' ) . '">🔄 ' . esc_html__( 'Recheck', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
        echo '</td>';
        echo '</tr>';
    }
    echo '</tbody></table>';
    // Pagination - using centralized function.
    $preserve_params = array(
        'page'     => 'wuclean-products',
        'tab'      => 'issues',
        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
    );
    if ( !empty( $search_term ) ) {
        $preserve_params['search'] = $search_term;
    }
    if ( !empty( $sort_column ) ) {
        $preserve_params['sort'] = $sort_column;
    }
    if ( !empty( $sort_order ) ) {
        $preserve_params['order'] = $sort_order;
    }
    pt_wuclean_render_pagination( array(
        'current_page'    => $current_page,
        'total_pages'     => $total_pages,
        'base_url'        => admin_url( 'admin.php' ),
        'page_param'      => 'paged',
        'preserve_params' => $preserve_params,
    ) );
    echo '</form>';
    // Add nonce for recheck functionality.
    echo '<input type="hidden" id="pt_wuclean_recheck_product_nonce" value="' . esc_attr( wp_create_nonce( 'pt_wuclean_recheck_product' ) ) . '">';
    echo '</div>';
}

/**
 * Orphaned Data Tab
 */
function pt_wuclean_render_orphaned_data_tab() {
    pt_wuclean_check_permissions();
    // Get orphaned data - try transient first, then option as fallback.
    $orphaned_data = get_transient( 'pt_wuclean_orphaned_data' );
    $orphaned_data = ( $orphaned_data ? $orphaned_data : get_option( 'pt_wuclean_orphaned_data', array() ) );
    // Basic error handling for orphaned data.
    if ( !is_array( $orphaned_data ) ) {
        $orphaned_data = array();
    }
    // Filter out corrupted/invalid items (items without proper type or ID).
    $orphaned_data = array_filter( $orphaned_data, function ( $item ) {
        // Item must be an array.
        if ( !is_array( $item ) ) {
            return false;
        }
        // Item must have a valid type (not empty, not 'unknown').
        if ( empty( $item['type'] ) || 'unknown' === $item['type'] ) {
            return false;
        }
        // Item must have an ID.
        if ( !isset( $item['id'] ) || '' === $item['id'] ) {
            return false;
        }
        return true;
    } );
    // Re-index array after filtering.
    $orphaned_data = array_values( $orphaned_data );
    // If we filtered out items, update the stored data to remove them permanently.
    $_od = get_transient( 'pt_wuclean_orphaned_data' );
    $_od = ( $_od ? $_od : get_option( 'pt_wuclean_orphaned_data', array() ) );
    $original_count = count( $_od );
    if ( count( $orphaned_data ) < $original_count ) {
        pt_wuclean_store_data_24h( 'pt_wuclean_orphaned_data', $orphaned_data );
    }
    // Limit large datasets to prevent performance issues.
    if ( count( $orphaned_data ) > 10000 ) {
        $orphaned_data = array_slice( $orphaned_data, 0, 1000 );
    }
    // Validate tab nonce before trusting any GET-based filters for this view.
    $products_tab_nonce_valid = isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'wuclean-products-tab' );
    // Search functionality.
    $search_term = '';
    $debug_search = false;
    if ( $products_tab_nonce_valid ) {
        if ( isset( $_GET['search'] ) ) {
            $search_term = sanitize_text_field( wp_unslash( $_GET['search'] ) );
        }
        if ( isset( $_GET['debug_search'] ) && current_user_can( pt_wuclean_get_required_capability() ) ) {
            $debug_search = true;
        }
    }
    // Basic filtering and pagination.
    $filtered_data = $orphaned_data;
    // Search filtering.
    if ( !empty( $search_term ) ) {
        $search_term_str = (string) $search_term;
        $filtered_data = array();
        foreach ( $orphaned_data as $item ) {
            // Ensure item is an array.
            if ( !is_array( $item ) ) {
                continue;
            }
            // Search across all fields.
            $searchable_text = implode( ' ', array(
                $item['id'] ?? '',
                $item['post_id'] ?? '',
                $item['meta_key'] ?? '',
                $item['meta_value'] ?? '',
                $item['description'] ?? '',
                $item['type'] ?? '',
                $item['term_name'] ?? '',
                $item['taxonomy'] ?? '',
                $item['count'] ?? '',
                $item['attribute_name'] ?? '',
                $item['attribute_values'] ?? ''
            ) );
            if ( stripos( $searchable_text, $search_term_str ) !== false ) {
                $filtered_data[] = $item;
            }
        }
    } else {
        $filtered_data = $orphaned_data;
    }
    // Pagination settings.
    $items_per_page = 25;
    // Standard pagination size for consistency.
    $current_page = 1;
    if ( $products_tab_nonce_valid && isset( $_GET['paged'] ) ) {
        $current_page = max( 1, intval( $_GET['paged'] ) );
    }
    $total_items = count( $filtered_data );
    $total_pages = ceil( $total_items / $items_per_page );
    $offset = ($current_page - 1) * $items_per_page;
    // Paginate the filtered data.
    $paginated_data = array_slice( $filtered_data, $offset, $items_per_page );
    echo '<div style="margin-top:2em; max-width: 1600px;">';
    echo '<h2>🗑️ ' . esc_html__( 'Product Orphan Data', 'wuclean-database-cleaner-for-woocommerce' ) . '</h2>';
    if ( pt_wuclean_fs()->is_not_paying() ) {
        echo '<div style="background: #ffe5e5; border: 1px solid #dc3232; border-radius: 4px; padding: 15px; margin: 20px 0;">';
        echo '<p style="margin: 0;"><strong>⚠️ ' . esc_html__( 'Warning:', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong> ' . esc_html__( 'This tab lists two kinds of findings: (A) broken references (meta/variations/comments/term links to missing posts) and (B) valid product tags that are not currently in use (pink rows). Selected items can be deleted using your favorite database manager or consider automating cleanup with Wuclean Pro👑', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
        echo '</div>';
    }
    // Simple fallback for empty data.
    if ( empty( $orphaned_data ) ) {
        echo '<div style="background: #fff; border: 1px solid #ccd0d4; border-radius: 4px; padding: 20px; margin: 20px 0; text-align: center;">';
        echo '<p style="margin: 0; color: #646970;">' . esc_html__( 'No orphaned data found.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
        echo '<p style="margin: 10px 0 0 0; color: #646970; font-size: 13px;">' . esc_html__( 'Any orphaned data identified by the cleanup routine will be listed here.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
        echo '</div>';
        echo '</div>';
        return;
    }
    // Search form.
    echo '<form method="get" action="' . esc_url( admin_url( 'admin.php' ) ) . '" style="margin-bottom: 20px;">';
    echo '<input type="hidden" name="page" value="wuclean-products">';
    echo '<input type="hidden" name="tab" value="orphaned_data">';
    echo '<input type="hidden" name="_wpnonce" value="' . esc_attr( wp_create_nonce( 'wuclean-products-tab' ) ) . '">';
    $search_placeholder = esc_attr__( 'Search by Product ID, Meta ID, Meta Key, Meta Value, or Type', 'wuclean-database-cleaner-for-woocommerce' );
    echo '<input type="text" name="search" value="' . esc_attr( $search_term ) . '" placeholder="' . esc_attr( $search_placeholder ) . '" style="width: 400px; padding: 5px; height: 30px; box-sizing: border-box;">';
    echo '<input type="submit" value="' . esc_attr__( 'Search', 'wuclean-database-cleaner-for-woocommerce' ) . '" class="button">';
    if ( !empty( $search_term ) ) {
        echo '<a href="' . esc_url( add_query_arg( array(
            'page'     => 'wuclean-products',
            'tab'      => 'orphaned_data',
            '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
        ), admin_url( 'admin.php' ) ) ) . '" class="button">' . esc_html__( 'Clear Search', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
    }
    echo '</form>';
    // Start the data table.
    echo '<form method="post" action="">';
    wp_nonce_field( 'pt_wuclean_orphaned_data', 'pt_wuclean_orphaned_data_nonce' );
    // Pagination info.
    if ( $total_items > 0 ) {
        echo '<div style="margin-bottom: 10px;">';
        $pagination_start = $offset + 1;
        $pagination_end = min( $offset + $items_per_page, $total_items );
        if ( !empty( $search_term ) ) {
            /* translators: %1$d: start number, %2$d: end number, %3$d: total count. */
            $template = esc_html__( 'Showing %1$d-%2$d of %3$d items (filtered by search)', 'wuclean-database-cleaner-for-woocommerce' );
        } else {
            /* translators: %1$d: start number, %2$d: end number, %3$d: total count. */
            $template = esc_html__( 'Showing %1$d-%2$d of %3$d items', 'wuclean-database-cleaner-for-woocommerce' );
        }
        echo '<span class="pt-wuclean-pagination" data-template="' . esc_attr( $template ) . '" data-start="' . esc_attr( $pagination_start ) . '" data-end="' . esc_attr( $pagination_end ) . '" data-total="' . esc_attr( $total_items ) . '">' . esc_html( sprintf(
            $template,
            $pagination_start,
            $pagination_end,
            $total_items
        ) ) . '</span>';
        echo '</div>';
    }
    // Bulk action buttons - standardized pattern.
    if ( !empty( $paginated_data ) ) {
        echo '<div style="margin-bottom: 20px;">';
        echo '<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">';
        echo '</div>';
        echo '</div>';
    }
    echo '<table class="wp-list-table widefat striped" style="margin-top: 20px; width: 100%; table-layout: auto;">';
    echo '<thead>';
    echo '<tr>';
    echo '<th style="text-align: left; min-width: 60px;">' . esc_html__( 'ID', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '<th style="text-align: left; min-width: 100px;">' . esc_html__( 'Type', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '<th style="text-align: left; min-width: 80px;">' . esc_html__( 'Post ID', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '<th style="min-width: 160px;">' . esc_html__( 'Meta Key/Taxonomy/Attribute', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '<th style="min-width: 200px;">' . esc_html__( 'Meta Value/Count/Values', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '<th style="min-width: 220px;">' . esc_html__( 'Description', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '</tr>';
    echo '</thead>';
    echo '<tbody>';
    if ( empty( $paginated_data ) ) {
        $colspan = 6;
        echo '<tr><td colspan="' . esc_attr( $colspan ) . '" style="text-align: center; padding: 20px;">' . esc_html__( 'No orphaned data found.', 'wuclean-database-cleaner-for-woocommerce' ) . '</td></tr>';
    } else {
        $row_index = 0;
        foreach ( $paginated_data as $item ) {
            $item_type = ( isset( $item['type'] ) ? $item['type'] : 'unknown' );
            $item_id = ( isset( $item['id'] ) ? $item['id'] : '' );
            // Type-specific colors with hover effect.
            if ( 'product_variation' === $item_type ) {
                $bg_color = '#fff3cd';
            } elseif ( 'unused_product_tag' === $item_type ) {
                $bg_color = '#f8d7da';
            } elseif ( 'unused_custom_attribute' === $item_type ) {
                $bg_color = '#d1ecf1';
            } else {
                $bg_color = ( 0 === $row_index % 2 ? '#ffffff' : '#f9f9f9' );
            }
            echo '<tr style="background-color: ' . esc_attr( $bg_color ) . '; transition: background-color 0.2s;" onmouseover="this.style.backgroundColor=\'#e8f4f8\'" onmouseout="this.style.backgroundColor=\'' . esc_attr( $bg_color ) . '\'">';
            ++$row_index;
            // ID.
            echo '<td style="text-align: left;">' . esc_html( $item_id ) . '</td>';
            // Type.
            $type_label = ucfirst( str_replace( '_', ' ', $item_type ) );
            echo '<td style="text-align: left;">' . esc_html( $type_label ) . '</td>';
            // Post ID.
            echo '<td style="text-align: left;">' . esc_html( ( isset( $item['post_id'] ) ? $item['post_id'] : $item_id ) ) . '</td>';
            // Meta Key (for meta items) or Taxonomy (for tags) or Attribute Name.
            if ( isset( $item['meta_key'] ) ) {
                echo '<td style="text-align: left;">' . esc_html( $item['meta_key'] ) . '</td>';
            } elseif ( isset( $item['taxonomy'] ) ) {
                echo '<td style="text-align: left;">' . esc_html( $item['taxonomy'] ) . '</td>';
            } elseif ( isset( $item['attribute_name'] ) ) {
                echo '<td style="text-align: left;">' . esc_html( $item['attribute_name'] ) . '</td>';
            } else {
                echo '<td style="text-align: center;">-</td>';
            }
            // Meta Value (for meta items) or Count (for tags) or Attribute Values.
            if ( isset( $item['meta_value'] ) ) {
                $meta_value = $item['meta_value'];
                if ( strlen( $meta_value ) > 50 ) {
                    $meta_value = substr( $meta_value, 0, 50 ) . '...';
                }
                echo '<td style="text-align: left;">' . esc_html( $meta_value ) . '</td>';
            } elseif ( isset( $item['count'] ) ) {
                echo '<td style="text-align: center;">' . esc_html( $item['count'] ) . '</td>';
            } elseif ( isset( $item['attribute_values'] ) ) {
                $attr_values = ( is_array( $item['attribute_values'] ) ? implode( ', ', $item['attribute_values'] ) : $item['attribute_values'] );
                if ( strlen( $attr_values ) > 50 ) {
                    $attr_values = substr( $attr_values, 0, 50 ) . '...';
                }
                echo '<td style="text-align: left;">' . esc_html( $attr_values ) . '</td>';
            } else {
                echo '<td style="text-align: center;">-</td>';
            }
            // Description.
            $description = ( isset( $item['description'] ) ? $item['description'] : '' );
            echo '<td style="text-align: left;">' . esc_html( $description ) . '</td>';
            echo '</tr>';
        }
    }
    echo '</tbody>';
    echo '</table>';
    // Pagination - using centralized function.
    $preserve_params = array(
        'page'     => 'wuclean-products',
        'tab'      => 'orphaned_data',
        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
    );
    if ( !empty( $search_term ) ) {
        $preserve_params['search'] = $search_term;
    }
    pt_wuclean_render_pagination( array(
        'current_page'    => $current_page,
        'total_pages'     => $total_pages,
        'base_url'        => admin_url( 'admin.php' ),
        'page_param'      => 'paged',
        'preserve_params' => $preserve_params,
    ) );
    echo '</form>';
    echo '</div>';
}

/**
 * Unused Global Attributes Tab.
 */
function pt_wuclean_render_unused_attributes_tab() {
    pt_wuclean_check_permissions();
    // Get unused attributes data - try transient first, then option as fallback.
    $unused_attributes = get_transient( 'pt_wuclean_unused_attributes' );
    $unused_attributes = ( $unused_attributes ? $unused_attributes : get_option( 'pt_wuclean_unused_attributes', array() ) );
    // Basic error handling for unused attributes data.
    if ( !is_array( $unused_attributes ) ) {
        $unused_attributes = array();
    }
    echo '<div style="margin-top:2em;">';
    echo '<h2>🏷️ ' . esc_html__( 'Unused Global Attributes', 'wuclean-database-cleaner-for-woocommerce' ) . '</h2>';
    // Info box.
    echo '<div style="background: #e7f3ff; border: 1px solid #2196f3; border-radius: 4px; padding: 15px; margin: 20px 0;">';
    echo '<p style="margin: 0;"><strong>ℹ️ ' . esc_html__( 'About Unused Attributes:', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong> ' . esc_html__( 'This tab shows global product attributes (like color, size, material) that are not currently being used by any active products. ', 'wuclean-database-cleaner-for-woocommerce' );
    echo sprintf( 
        // translators: %1$s: opening link tag, %2$s: closing link tag.
        esc_html__( 'These can be managed via WooCommerce %1$sProduct Attributes%2$s.', 'wuclean-database-cleaner-for-woocommerce' ),
        '<a href="' . esc_url( admin_url( 'edit.php?post_type=product&page=product_attributes' ) ) . '" target="_blank" rel="noopener noreferrer">',
        '</a>'
     ) . '</p>';
    echo '</div>';
    // Simple fallback for empty data.
    if ( empty( $unused_attributes ) ) {
        echo '<div style="background: #fff; border: 1px solid #ccd0d4; border-radius: 4px; padding: 20px; margin: 20px 0; text-align: center;">';
        echo '<p style="margin: 0; color: #646970;">' . esc_html__( 'No unused global attributes found.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
        echo '<p style="margin: 10px 0 0 0; color: #646970; font-size: 13px;">' . esc_html__( 'Any unused attributes identified by the cleanup routine will be listed here.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
        echo '</div>';
        echo '</div>';
        return;
    }
    // Search form - standardized pattern.
    // Verify nonce - required for security (CSRF protection).
    // Accept tab nonce since search form uses it, or search nonce for compatibility.
    if ( isset( $_GET['search'] ) ) {
        $nonce_valid = false;
        if ( isset( $_GET['_wpnonce'] ) ) {
            $nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) );
            $nonce_valid = wp_verify_nonce( $nonce, 'wuclean-products-tab' ) || wp_verify_nonce( $nonce, 'wuclean-products-search' );
        }
        if ( $nonce_valid ) {
            $search_term = sanitize_text_field( wp_unslash( $_GET['search'] ) );
        } else {
            // Nonce verification failed - ignore search parameter for security.
            $search_term = '';
        }
    } else {
        $search_term = '';
    }
    echo '<form method="get" action="' . esc_url( admin_url( 'admin.php' ) ) . '" style="margin-bottom: 20px;">';
    echo '<input type="hidden" name="page" value="wuclean-products">';
    echo '<input type="hidden" name="tab" value="unused_attributes">';
    echo '<input type="hidden" name="_wpnonce" value="' . esc_attr( wp_create_nonce( 'wuclean-products-tab' ) ) . '">';
    $search_placeholder = esc_attr__( 'Search by Attribute Name, Slug, or Term Name', 'wuclean-database-cleaner-for-woocommerce' );
    echo '<input type="text" name="search" value="' . esc_attr( $search_term ) . '" placeholder="' . esc_attr( $search_placeholder ) . '" style="width: 400px; padding: 5px; height: 30px; box-sizing: border-box;">';
    echo '<input type="submit" value="' . esc_attr__( 'Search', 'wuclean-database-cleaner-for-woocommerce' ) . '" class="button">';
    if ( !empty( $search_term ) ) {
        echo '<a href="' . esc_url( add_query_arg( array(
            'page'     => 'wuclean-products',
            'tab'      => 'unused_attributes',
            '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
        ), admin_url( 'admin.php' ) ) ) . '" class="button">' . esc_html__( 'Clear Search', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
    }
    echo '</form>';
    // Apply search filtering.
    $filtered_attributes = $unused_attributes;
    if ( !empty( $search_term ) ) {
        $search_term_str = (string) $search_term;
        $filtered_attributes = array_filter( $unused_attributes, function ( $attribute ) use($search_term_str) {
            // Search across all fields.
            $searchable_text = implode( ' ', array(
                $attribute['attribute_name'] ?? '',
                $attribute['attribute_slug'] ?? '',
                $attribute['term_name'] ?? '',
                $attribute['term_slug'] ?? ''
            ) );
            return stripos( $searchable_text, $search_term_str ) !== false;
        } );
    }
    // Check if we have filtered results.
    if ( empty( $filtered_attributes ) ) {
        if ( !empty( $search_term ) ) {
            // translators: %s: search term entered by user.
            echo '<p>' . sprintf( esc_html__( 'No unused attributes found matching "%s".', 'wuclean-database-cleaner-for-woocommerce' ), esc_html( $search_term ) ) . '</p>';
        } else {
            echo '<p>' . esc_html__( 'No unused global attributes are currently listed. Any new unused attributes identified by the cleanup routine will be listed here in future.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
        }
        return;
    }
    // Bulk actions.
    echo '<div style="margin-bottom: 20px;">';
    echo '<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">';
    echo '</div>';
    echo '</div>';
    // Pagination controls.
    $items_per_page = 25;
    // Standard pagination size for consistency.
    $current_page = 1;
    if ( isset( $_GET['paged'] ) ) {
        // Verify nonce - required for security (CSRF protection).
        // Accept tab, pagination, or search nonce since pagination can occur on search results or regular browsing.
        $nonce_valid = false;
        if ( isset( $_GET['_wpnonce'] ) ) {
            $nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) );
            $nonce_valid = wp_verify_nonce( $nonce, 'wuclean-products-tab' ) || wp_verify_nonce( $nonce, 'wuclean-products-pagination' ) || wp_verify_nonce( $nonce, 'wuclean-products-search' );
        }
        if ( $nonce_valid ) {
            $current_page = max( 1, intval( $_GET['paged'] ) );
        }
        // If nonce verification failed, default to page 1 for security.
    }
    $total_items = count( $filtered_attributes );
    $total_pages = ceil( $total_items / $items_per_page );
    $offset = ($current_page - 1) * $items_per_page;
    // Pagination info.
    if ( $total_items > 0 ) {
        echo '<div style="margin-bottom: 10px;">';
        $pagination_start = $offset + 1;
        $pagination_end = min( $offset + $items_per_page, $total_items );
        if ( !empty( $search_term ) ) {
            /* translators: %1$d: start number, %2$d: end number, %3$d: total count. */
            $template = esc_html__( 'Showing %1$d-%2$d of %3$d items (filtered by search)', 'wuclean-database-cleaner-for-woocommerce' );
        } else {
            /* translators: %1$d: start number, %2$d: end number, %3$d: total count. */
            $template = esc_html__( 'Showing %1$d-%2$d of %3$d items', 'wuclean-database-cleaner-for-woocommerce' );
        }
        echo '<span class="pt-wuclean-pagination" data-template="' . esc_attr( $template ) . '" data-start="' . esc_attr( $pagination_start ) . '" data-end="' . esc_attr( $pagination_end ) . '" data-total="' . esc_attr( $total_items ) . '">' . esc_html( sprintf(
            $template,
            $pagination_start,
            $pagination_end,
            $total_items
        ) ) . '</span>';
        echo '</div>';
    }
    // Main table.
    echo '<table class="wp-list-table widefat fixed striped" style="margin-top: 20px;">';
    echo '<thead>';
    echo '<tr>';
    echo '<th style="width: 200px;">' . esc_html__( 'Attribute Name', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '<th style="width: 150px;">' . esc_html__( 'Slug', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '<th style="width: 200px;">' . esc_html__( 'Term Name', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '<th style="width: 100px;">' . esc_html__( 'Usage', 'wuclean-database-cleaner-for-woocommerce' ) . '</th>';
    echo '</tr>';
    echo '</thead>';
    echo '<tbody>';
    // Get paginated data.
    $paginated_attributes = array_slice( $filtered_attributes, $offset, $items_per_page );
    if ( empty( $paginated_attributes ) ) {
        $colspan = 4;
        echo '<tr><td colspan="' . esc_attr( $colspan ) . '" style="text-align: center; padding: 20px;">' . esc_html__( 'No unused attributes found.', 'wuclean-database-cleaner-for-woocommerce' ) . '</td></tr>';
    } else {
        $row_index = 0;
        foreach ( $paginated_attributes as $attribute ) {
            $attribute_name = esc_html( $attribute['attribute_name'] ?? '' );
            $attribute_slug = esc_html( $attribute['attribute_slug'] ?? '' );
            $term_name = esc_html( $attribute['term_name'] ?? '' );
            $term_slug = esc_html( $attribute['term_slug'] ?? '' );
            $usage_count = intval( $attribute['usage_count'] ?? 0 );
            $term_id = intval( $attribute['id'] ?? 0 );
            $term_taxonomy_id = intval( $attribute['term_taxonomy_id'] ?? 0 );
            $bg_color = ( 0 === $row_index % 2 ? '#ffffff' : '#f9f9f9' );
            echo '<tr data-term-id="' . absint( $term_id ) . '" data-term-taxonomy-id="' . absint( $term_taxonomy_id ) . '" style="background-color: ' . esc_attr( $bg_color ) . '; transition: background-color 0.2s;" onmouseover="this.style.backgroundColor=\'#e8f4f8\'" onmouseout="this.style.backgroundColor=\'' . esc_attr( $bg_color ) . '\'">';
            ++$row_index;
            echo '<td><a href="' . esc_url( admin_url( 'edit.php?post_type=product&page=product_attributes' ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $attribute_name ) . '</a></td>';
            echo '<td>' . esc_html( $attribute_slug ) . '</td>';
            echo '<td><a href="' . esc_url( admin_url( 'edit-tags.php?taxonomy=' . $attribute_slug . '&tag_ID=' . $term_id ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $term_name ) . '</a></td>';
            echo '<td>' . absint( $usage_count ) . '</td>';
            echo '</tr>';
        }
    }
    echo '</tbody>';
    echo '</table>';
    // Pagination - using centralized function (after table).
    $preserve_params = array(
        'page'     => 'wuclean-products',
        'tab'      => 'unused_attributes',
        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
    );
    if ( !empty( $search_term ) ) {
        $preserve_params['search'] = $search_term;
        // Use search nonce if search term is present.
        $preserve_params['_wpnonce'] = wp_create_nonce( 'wuclean-products-search' );
    }
    pt_wuclean_render_pagination( array(
        'current_page'    => $current_page,
        'total_pages'     => $total_pages,
        'base_url'        => admin_url( 'admin.php' ),
        'page_param'      => 'paged',
        'preserve_params' => $preserve_params,
    ) );
    echo '</div>';
}

/**
 * AJAX handler to start product search
 */
function pt_wuclean_ajax_start_product_search() {
    pt_wuclean_check_ajax_permissions();
    if ( !isset( $_POST['nonce'] ) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'pt_wuclean_product_search' ) ) {
        wp_send_json_error( array(
            'message' => __( 'Invalid nonce', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
        return;
    }
    // Clear existing cached results at the start (both transient and option).
    delete_transient( 'pt_wuclean_product_issues' );
    delete_option( 'pt_wuclean_product_issues' );
    // Clear old progress data from previous runs.
    delete_option( 'pt_wuclean_product_search_progress' );
    // Flush cache to ensure deletions take effect.
    wp_cache_delete( 'pt_wuclean_product_search_progress', 'options' );
    // Get settings and total product count.
    global $wpdb;
    $settings = get_option( 'pt_wuclean_settings', array() );
    $batch_size = $settings['product_cleanup_batch_size'] ?? 100;
    $total_products = $wpdb->get_var( "SELECT COUNT(*) \n         FROM {$wpdb->posts} \n         WHERE post_type IN ('product', 'product_variation') \n         AND post_status NOT IN ('auto-draft', 'trash')" );
    $total_batches = ceil( $total_products / $batch_size );
    // Clear any batch transients from previous runs.
    for ($i = 0; $i < $total_batches + 5; $i++) {
        // +5 buffer for safety
        delete_transient( 'pt_wuclean_product_issues_batch_' . $i );
    }
    // Initialize search progress.
    $progress_data = array(
        'status'        => 'running',
        'total_items'   => 0,
        'processed'     => 0,
        'issues_found'  => 0,
        'current_batch' => 0,
        'total_batches' => 0,
        'start_time'    => time(),
    );
    $progress_data['total_items'] = $total_products;
    $progress_data['total_batches'] = $total_batches;
    // Store progress data.
    pt_wuclean_update_progress( 'pt_wuclean_product_search_progress', $progress_data );
    wp_send_json_success( array(
        'message'  => __( 'Product search started successfully.', 'wuclean-database-cleaner-for-woocommerce' ),
        'progress' => $progress_data,
    ) );
}

/**
 * AJAX handler to process next product search batch
 * This is called repeatedly by JavaScript to process one batch at a time
 */
function pt_wuclean_ajax_process_product_batch() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_product_search', 'nonce' );
    // Get current progress.
    $progress_data = get_option( 'pt_wuclean_product_search_progress', array() );
    if ( empty( $progress_data ) ) {
        wp_send_json_error( array(
            'message' => __( 'No product search found.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
    $current_batch = $progress_data['current_batch'] ?? 0;
    $total_batches = $progress_data['total_batches'] ?? 0;
    // Check if search is already completed (aggregation happens in batch processor now).
    if ( 'completed' === $progress_data['status'] ) {
        wp_send_json_success( array(
            'message'   => __( 'All batches completed.', 'wuclean-database-cleaner-for-woocommerce' ),
            'completed' => true,
            'progress'  => $progress_data,
        ) );
    }
    // Check if status is running.
    if ( 'running' !== ($progress_data['status'] ?? '') ) {
        wp_send_json_error( array(
            'message' => __( 'Product search is not running.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
    // Process the next batch.
    $settings = get_option( 'pt_wuclean_settings', array() );
    $batch_size = $settings['product_cleanup_batch_size'] ?? 100;
    pt_wuclean_process_product_search_batch( $current_batch, $batch_size );
    // Get updated progress (may have changed to 'completed' if this was the last batch).
    $progress_data = get_option( 'pt_wuclean_product_search_progress', array() );
    // Check if completed after processing.
    $is_completed = ($progress_data['status'] ?? '') === 'completed';
    $completed_msg = __( 'All batches completed.', 'wuclean-database-cleaner-for-woocommerce' );
    /* translators: %1$d: current batch number, %2$d: total batches. */
    $batch_progress_fmt = __( 'Processed batch %1$d of %2$d', 'wuclean-database-cleaner-for-woocommerce' );
    $batch_message = ( $is_completed ? $completed_msg : sprintf( $batch_progress_fmt, $current_batch + 1, $total_batches ) );
    wp_send_json_success( array(
        'message'   => $batch_message,
        'completed' => $is_completed,
        'progress'  => $progress_data,
    ) );
}

/**
 * AJAX handler to get product search progress
 */
function pt_wuclean_ajax_get_product_search_progress() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_product_search', 'nonce' );
    $progress_data = get_option( 'pt_wuclean_product_search_progress', array() );
    if ( empty( $progress_data ) ) {
        wp_send_json_error( array(
            'message' => __( 'No search in progress.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
    // Calculate percentage.
    $percentage = 0;
    if ( !empty( $progress_data['total_items'] ) && $progress_data['total_items'] > 0 ) {
        $percentage = min( 100, round( $progress_data['processed'] / $progress_data['total_items'] * 100, 1 ) );
    }
    $progress_data['percentage'] = $percentage;
    wp_send_json_success( $progress_data );
}

/**
 * AJAX handler to clear product search progress
 */
function pt_wuclean_ajax_clear_search_progress() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_product_search', 'nonce' );
    // Clear any scheduled cron events if applicable.
    wp_clear_scheduled_hook( 'pt_wuclean_process_product_search_batch' );
    // Clear progress data.
    delete_option( 'pt_wuclean_product_search_progress' );
    wp_send_json_success( array(
        'message' => __( 'Search progress cleared successfully.', 'wuclean-database-cleaner-for-woocommerce' ),
    ) );
}

/**
 * AJAX handler to stop product search
 */
function pt_wuclean_ajax_stop_product_search() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_product_search', 'nonce' );
    // Clear any scheduled cron events if applicable.
    wp_clear_scheduled_hook( 'pt_wuclean_process_product_search_batch' );
    // Clear progress data.
    delete_option( 'pt_wuclean_product_search_progress' );
    wp_send_json_success( array(
        'message' => __( 'Product search stopped successfully.', 'wuclean-database-cleaner-for-woocommerce' ),
    ) );
}

/**
 * AJAX handler to start orphaned search
 */
function pt_wuclean_ajax_start_orphaned_search() {
    pt_wuclean_check_ajax_permissions();
    if ( !isset( $_POST['nonce'] ) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'pt_wuclean_orphaned_search' ) ) {
        wp_send_json_error( array(
            'message' => __( 'Invalid nonce', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
        return;
    }
    // Clear existing results (both transient and option).
    delete_transient( 'pt_wuclean_orphaned_data' );
    delete_option( 'pt_wuclean_orphaned_data' );
    // Clear old progress data from previous runs.
    delete_option( 'pt_wuclean_orphaned_search_progress' );
    // Clear temp accumulator data from previous runs.
    delete_option( 'pt_wuclean_orphaned_data_temp' );
    // Flush cache to ensure deletions take effect.
    wp_cache_delete( 'pt_wuclean_orphaned_search_progress', 'options' );
    wp_cache_delete( 'pt_wuclean_orphaned_data_temp', 'options' );
    // Initialize search progress with multi-step state tracking.
    $progress_data = array(
        'status'                       => 'running',
        'current_step'                 => 'postmeta',
        'current_batch'                => 0,
        'step_offset'                  => 0,
        'total_items'                  => 0,
        'processed'                    => 0,
        'orphaned_meta_found'          => 0,
        'orphaned_variations_found'    => 0,
        'orphaned_comments_found'      => 0,
        'orphaned_relationships_found' => 0,
        'unused_tags_found'            => 0,
        'unused_attributes_found'      => 0,
        'start_time'                   => time(),
        'last_activity'                => time(),
        'message'                      => __( 'Initializing orphaned data search...', 'wuclean-database-cleaner-for-woocommerce' ),
    );
    // Store progress data.
    update_option( 'pt_wuclean_orphaned_search_progress', $progress_data, false );
    wp_send_json_success( array(
        'message'  => __( 'Orphaned data search started successfully.', 'wuclean-database-cleaner-for-woocommerce' ),
        'progress' => $progress_data,
    ) );
}

/**
 * AJAX handler to process next orphaned search batch
 * This processes different types of orphaned data step by step
 */
function pt_wuclean_ajax_process_orphaned_batch() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_orphaned_search', 'nonce' );
    // Get current progress.
    $progress_data = get_option( 'pt_wuclean_orphaned_search_progress', array() );
    if ( empty( $progress_data ) ) {
        wp_send_json_error( array(
            'message' => __( 'No orphaned search found.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
    // Check if search is already completed.
    if ( 'completed' === $progress_data['status'] ) {
        wp_send_json_success( array(
            'message'   => __( 'All orphaned data search completed.', 'wuclean-database-cleaner-for-woocommerce' ),
            'completed' => true,
            'progress'  => $progress_data,
        ) );
    }
    // Check if status is running.
    if ( 'running' !== ($progress_data['status'] ?? '') ) {
        wp_send_json_error( array(
            'message' => __( 'Orphaned search is not running.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
    // Process the next step/batch.
    $settings = get_option( 'pt_wuclean_settings', array() );
    $batch_size = $settings['product_cleanup_batch_size'] ?? 100;
    // Note: Ignored - processes all data at once.
    pt_wuclean_process_orphaned_data_unified( $batch_size );
    // Get updated progress (may have changed to 'completed').
    $progress_data = get_option( 'pt_wuclean_orphaned_search_progress', array() );
    // Check if completed after processing.
    $is_completed = ($progress_data['status'] ?? '') === 'completed';
    // If completed, include stored data count for redirect logic.
    if ( $is_completed ) {
        $stored_data = get_transient( 'pt_wuclean_orphaned_data' );
        $stored_data = ( $stored_data ? $stored_data : get_option( 'pt_wuclean_orphaned_data', array() ) );
        $progress_data['stored_data_count'] = ( is_array( $stored_data ) ? count( $stored_data ) : 0 );
    }
    wp_send_json_success( array(
        'message'   => ( $is_completed ? __( 'Orphaned data search completed.', 'wuclean-database-cleaner-for-woocommerce' ) : $progress_data['message'] ?? __( 'Processing...', 'wuclean-database-cleaner-for-woocommerce' ) ),
        'completed' => $is_completed,
        'progress'  => $progress_data,
    ) );
}

/**
 * Process ALL orphaned data in one pass.
 *
 * @param int $batch_size Batch size (used if delegating to scheduled processor).
 */
function pt_wuclean_process_orphaned_data_batch(  $batch_size  ) {
    // Set execution time limit to prevent timeouts.
    // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged, WordPress.PHP.NoSilencedErrors.Discouraged -- set_time_limit may be disabled; @ fails gracefully.
    @set_time_limit( 60 );
    // 60 seconds - conservative limit for shared hosting compatibility.
    // Get current progress.
    $progress_data = get_option( 'pt_wuclean_orphaned_search_progress', array() );
    $progress_data['last_activity'] = time();
    $progress_data['message'] = __( 'Processing all orphaned data...', 'wuclean-database-cleaner-for-woocommerce' );
    update_option( 'pt_wuclean_orphaned_search_progress', $progress_data, false );
    // Process ALL orphaned data at once (core detect + unused tags + shared normalization).
    $orphaned_data = pt_wuclean_build_full_orphaned_detect_results( $batch_size );
    // Store final results (both transient and option).
    pt_wuclean_store_data_24h( 'pt_wuclean_orphaned_data', $orphaned_data );
    // Mark as completed with summary.
    $progress_data['status'] = 'completed';
    $progress_data['processed'] = count( $orphaned_data );
    // Include stored data count for redirect logic (may include items from previous scans).
    $stored_data = get_transient( 'pt_wuclean_orphaned_data' );
    $stored_data = ( $stored_data ? $stored_data : get_option( 'pt_wuclean_orphaned_data', array() ) );
    $progress_data['stored_data_count'] = ( is_array( $stored_data ) ? count( $stored_data ) : 0 );
    // translators: %d: number of orphaned items.
    $progress_data['message'] = sprintf( __( 'Search completed! Found %d orphaned items.', 'wuclean-database-cleaner-for-woocommerce' ), count( $orphaned_data ) );
    update_option( 'pt_wuclean_orphaned_search_progress', $progress_data, false );
    // Memory cleanup.
    if ( function_exists( 'gc_collect_cycles' ) ) {
        gc_collect_cycles();
    }
}

/**
 * AJAX handler to get orphaned search progress.
 */
function pt_wuclean_ajax_get_orphaned_search_progress() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_orphaned_search', 'nonce' );
    $progress_data = get_option( 'pt_wuclean_orphaned_search_progress', array() );
    if ( empty( $progress_data ) ) {
        wp_send_json_error( array(
            'message' => __( 'No orphaned search in progress.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
    // Calculate percentage.
    $percentage = 0;
    if ( !empty( $progress_data['total_items'] ) && $progress_data['total_items'] > 0 ) {
        $percentage = min( 100, round( $progress_data['processed'] / $progress_data['total_items'] * 100, 1 ) );
    }
    $progress_data['percentage'] = $percentage;
    wp_send_json_success( $progress_data );
}

/**
 * AJAX handler to clear orphaned search progress
 */
function pt_wuclean_ajax_clear_orphaned_search_progress() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_orphaned_search', 'nonce' );
    delete_option( 'pt_wuclean_orphaned_search_progress' );
    // Don't clear the orphaned data results - only clear the search progress.
    wp_send_json_success( array(
        'message' => __( 'Orphaned search progress cleared successfully.', 'wuclean-database-cleaner-for-woocommerce' ),
    ) );
}

/**
 * AJAX handler to stop orphaned search
 */
function pt_wuclean_ajax_stop_orphaned_search() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_orphaned_search', 'nonce' );
    // Clear the progress data and temp data.
    delete_option( 'pt_wuclean_orphaned_search_progress' );
    delete_option( 'pt_wuclean_orphaned_data_temp' );
    wp_send_json_success( array(
        'message' => __( 'Orphaned search stopped successfully.', 'wuclean-database-cleaner-for-woocommerce' ),
    ) );
}

/**
 * Intelligently identify which orphaned post IDs were likely WooCommerce products.
 *
 * @param array<int, array<int, object>> $orphaned_meta_by_post Post ID => meta entries.
 * @return array<int> Likely product post IDs.
 */
function pt_wuclean_identify_likely_product_posts(  $orphaned_meta_by_post  ) {
    $likely_product_post_ids = array();
    // Define strong product indicators (multiple of these suggest a product).
    $strong_product_indicators = array(
        '_price',
        '_regular_price',
        '_sale_price',
        // Pricing.
        '_stock',
        '_stock_status',
        '_manage_stock',
        '_backorders',
        // Inventory.
        '_sku',
        '_weight',
        '_length',
        '_width',
        '_height',
        // Product attributes.
        '_downloadable',
        '_virtual',
        '_tax_status',
        // Product settings.
        '_featured',
        '_catalog_visibility',
        '_sold_individually',
        // Display settings.
        '_purchase_note',
        '_upsell_ids',
        '_crosssell_ids',
        // Product relationships.
        '_product_attributes',
        '_product_image_gallery',
        // Product data.
        '_product_type',
        '_product_visibility',
        // Core product settings.
        '_product_version',
    );
    // Define weak product indicators (single occurrence might not indicate product).
    $weak_product_indicators = array('_product_', '_woocommerce_', '_wc_');
    foreach ( $orphaned_meta_by_post as $post_id => $meta_entries ) {
        $product_score = 0;
        $strong_indicators_found = array();
        $weak_indicators_found = array();
        // Analyze each meta entry for this post.
        foreach ( $meta_entries as $meta ) {
            $meta_key = $meta->meta_key;
            // Check for strong indicators.
            if ( in_array( $meta_key, $strong_product_indicators, true ) ) {
                $product_score += 3;
                // Strong indicator.
                $strong_indicators_found[] = $meta_key;
            }
            // Check for weak indicators.
            foreach ( $weak_product_indicators as $weak_indicator ) {
                if ( 0 === strpos( $meta_key, $weak_indicator ) ) {
                    ++$product_score;
                    // Weak indicator.
                    $weak_indicators_found[] = $meta_key;
                    break;
                }
            }
        }
        // Determine if this post was likely a product.
        // Criteria: At least 2 strong indicators OR 1 strong + 2 weak indicators.
        $strong_count = count( $strong_indicators_found );
        $weak_count = count( $weak_indicators_found );
        if ( $strong_count >= 2 || $strong_count >= 1 && $weak_count >= 2 ) {
            $likely_product_post_ids[] = $post_id;
        }
    }
    return $likely_product_post_ids;
}

/**
 * Manual (AJAX) orphaned data detect scan — batched progress via pt_wuclean_process_orphaned_data_batch().
 *
 * @param int $batch_size Passed to the batch processor.
 * @return int Always -1 (batch processing in progress).
 */
function pt_wuclean_process_orphaned_data_unified(  $batch_size = 100  ) {
    // Set execution time limit to prevent timeouts.
    // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged, WordPress.PHP.NoSilencedErrors.Discouraged -- set_time_limit may be disabled; @ fails gracefully.
    @set_time_limit( 60 );
    // 60 seconds - conservative limit for shared hosting compatibility.
    pt_wuclean_process_orphaned_data_batch( $batch_size );
    return -1;
    // Indicates batch processing is in progress.
}

/**
 * One-pass orphaned product DB scan: core detect list unless a delete count is requested.
 * Detect mode is used by manual AJAX search via pt_wuclean_build_full_orphaned_detect_results())
 * No LIMIT on core queries; intelligent filtering focuses on product-related rows.
 *
 * @param string $mode       'detect' or 'delete'.
 * @param int    $batch_size UNUSED. Kept for compatibility.
 * @return array Results array for detect mode, deletion counts for delete mode.
 */
function pt_wuclean_process_orphaned_data_scheduled(  $mode = 'detect', $batch_size = 100  ) {
    // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $batch_size kept for compatibility
    global $wpdb;
    if ( 'delete' === $mode ) {
        return array(
            'product_meta'               => 0,
            'product_variations'         => 0,
            'product_comments'           => 0,
            'product_term_relationships' => 0,
        );
    }
    $orphaned_data = array();
    /* translators: %1$s: meta key, %2$d: product ID. */
    $product_meta_desc_fmt = __( 'Orphaned product meta: %1$s (Product ID: %2$d)', 'wuclean-database-cleaner-for-woocommerce' );
    /* translators: %1$d: variation ID, %2$d: parent ID. */
    $product_variation_desc_fmt = __( 'Orphaned product variation: ID %1$d (Parent: %2$d)', 'wuclean-database-cleaner-for-woocommerce' );
    /* translators: %1$d: comment ID, %2$d: product ID. */
    $product_comment_desc_fmt = __( 'Orphaned product comment: ID %1$d (Product: %2$d)', 'wuclean-database-cleaner-for-woocommerce' );
    /* translators: %1$s: term name, %2$s: taxonomy, %3$d: product ID. */
    $term_relationship_desc_fmt = __( 'Orphaned term relationship: %1$s (%2$s) for Product ID %3$d', 'wuclean-database-cleaner-for-woocommerce' );
    // Step 1: Find ALL orphaned product postmeta (no limit - intelligent filtering applied after).
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $orphaned_postmeta = $wpdb->get_results( "SELECT pm.meta_id, pm.post_id, pm.meta_key, pm.meta_value\n         FROM {$wpdb->postmeta} pm \n         LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID \n         WHERE p.ID IS NULL \n         AND pm.post_id IS NOT NULL" );
    // Group by post_id and identify likely product posts using intelligent scoring.
    $orphaned_meta_by_post = array();
    foreach ( $orphaned_postmeta as $meta ) {
        if ( !isset( $orphaned_meta_by_post[$meta->post_id] ) ) {
            $orphaned_meta_by_post[$meta->post_id] = array();
        }
        $orphaned_meta_by_post[$meta->post_id][] = $meta;
    }
    $likely_product_post_ids = pt_wuclean_identify_likely_product_posts( $orphaned_meta_by_post );
    foreach ( $likely_product_post_ids as $post_id ) {
        if ( isset( $orphaned_meta_by_post[$post_id] ) ) {
            foreach ( $orphaned_meta_by_post[$post_id] as $meta ) {
                $orphaned_data[] = array(
                    'type'        => 'product_meta',
                    'id'          => $meta->meta_id,
                    'post_id'     => $meta->post_id,
                    'meta_key'    => $meta->meta_key,
                    'meta_value'  => $meta->meta_value,
                    'description' => sprintf( $product_meta_desc_fmt, $meta->meta_key, $meta->post_id ),
                );
            }
        }
    }
    // Step 2: Find ALL orphaned product variations.
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $orphaned_variations = $wpdb->get_results( $wpdb->prepare( "SELECT v.ID, v.post_title, v.post_parent\n             FROM {$wpdb->posts} v\n             LEFT JOIN {$wpdb->posts} p ON v.post_parent = p.ID\n             WHERE v.post_type = %s\n             AND p.ID IS NULL", 'product_variation' ) );
    foreach ( $orphaned_variations as $variation ) {
        $orphaned_data[] = array(
            'type'        => 'product_variation',
            'id'          => $variation->ID,
            'post_title'  => $variation->post_title,
            'post_parent' => $variation->post_parent,
            'description' => sprintf( $product_variation_desc_fmt, $variation->ID, $variation->post_parent ),
        );
    }
    // Step 3: Find ALL orphaned product comments (reviews, product-related comments).
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $orphaned_product_comments = $wpdb->get_results( $wpdb->prepare( "SELECT c.comment_ID, c.comment_post_ID, c.comment_type, c.comment_content\n         FROM {$wpdb->comments} c\n         LEFT JOIN {$wpdb->posts} p ON c.comment_post_ID = p.ID\n         WHERE p.ID IS NULL\n         AND c.comment_post_ID IS NOT NULL\n         AND (c.comment_type = 'review' OR c.comment_type LIKE %s)", '%product%' ) );
    foreach ( $orphaned_product_comments as $comment ) {
        $orphaned_data[] = array(
            'type'            => 'product_comment',
            'id'              => $comment->comment_ID,
            'comment_post_ID' => $comment->comment_post_ID,
            'comment_type'    => $comment->comment_type,
            'comment_content' => substr( $comment->comment_content, 0, 100 ),
            'description'     => sprintf( $product_comment_desc_fmt, $comment->comment_ID, $comment->comment_post_ID ),
        );
    }
    // Step 4: Find ALL orphaned product term relationships (including product_type, categories, tags, attributes).
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $orphaned_product_term_relationships = $wpdb->get_results( $wpdb->prepare( "SELECT tr.object_id, tr.term_taxonomy_id, tt.taxonomy, t.name\n         FROM {$wpdb->term_relationships} tr\n         LEFT JOIN {$wpdb->posts} p ON tr.object_id = p.ID\n         LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id\n         LEFT JOIN {$wpdb->terms} t ON tt.term_id = t.term_id\n         WHERE p.ID IS NULL\n         AND tr.object_id IS NOT NULL\n         AND (tt.taxonomy = 'product_type' OR tt.taxonomy = 'product_cat' OR tt.taxonomy = 'product_tag' OR tt.taxonomy LIKE %s)", $wpdb->esc_like( 'pa_' ) . '%' ) );
    foreach ( $orphaned_product_term_relationships as $rel ) {
        $orphaned_data[] = array(
            'type'             => 'product_term_relationship',
            'id'               => $rel->object_id . '_' . $rel->term_taxonomy_id,
            'object_id'        => $rel->object_id,
            'term_taxonomy_id' => $rel->term_taxonomy_id,
            'taxonomy'         => $rel->taxonomy,
            'term_name'        => $rel->name,
            'description'      => sprintf(
                $term_relationship_desc_fmt,
                $rel->name,
                $rel->taxonomy,
                $rel->object_id
            ),
        );
    }
    return $orphaned_data;
}

/**
 * Fetch unused product tags (term count = 0) as orphaned-detect row payloads.
 *
 * @return array<int, array<string, mixed>>
 */
function pt_wuclean_get_unused_product_tag_orphaned_items() {
    global $wpdb;
    $unused_tags = $wpdb->get_results( $wpdb->prepare( "SELECT t.term_id, t.name, t.slug, tt.term_taxonomy_id, tt.count\n\t\t\tFROM {$wpdb->terms} t\n\t\t\tINNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id\n\t\t\tWHERE tt.taxonomy = %s\n\t\t\tAND tt.count = %d\n\t\t\tORDER BY t.name", 'product_tag', 0 ) );
    if ( !is_array( $unused_tags ) ) {
        return array();
    }
    $items = array();
    foreach ( $unused_tags as $tag ) {
        $items[] = array(
            'type'             => 'unused_product_tag',
            'id'               => $tag->term_id,
            'term_taxonomy_id' => $tag->term_taxonomy_id,
            'term_name'        => $tag->name,
            'term_slug'        => $tag->slug,
            'taxonomy'         => 'product_tag',
            'count'            => $tag->count,
            'description'      => sprintf( 
                // translators: %1$s: tag name, %2$d: usage count.
                __( 'Unused product tag: %1$s (Count: %2$d)', 'wuclean-database-cleaner-for-woocommerce' ),
                $tag->name,
                $tag->count
             ),
        );
    }
    return $items;
}

/**
 * Drop invalid rows from a detect-mode orphaned list.
 *
 * @param array $orphaned_data Raw list from DB-driven detection.
 * @return array<int, array<string, mixed>>
 */
function pt_wuclean_normalize_orphaned_detect_items(  $orphaned_data  ) {
    if ( !is_array( $orphaned_data ) ) {
        return array();
    }
    $orphaned_data = array_filter( $orphaned_data, function ( $item ) {
        return is_array( $item ) && !empty( $item['type'] ) && 'unknown' !== $item['type'] && isset( $item['id'] ) && '' !== $item['id'];
    } );
    return array_values( $orphaned_data );
}

/**
 * Build the full orphaned detect result set: core detect + unused product tags + normalization.
 *
 * @param int $batch_size Passed to pt_wuclean_process_orphaned_data_scheduled() (detect only).
 * @return array<int, array<string, mixed>> Normalized item list.
 */
function pt_wuclean_build_full_orphaned_detect_results(  $batch_size = 100  ) {
    $core = pt_wuclean_process_orphaned_data_scheduled( 'detect', $batch_size );
    if ( !is_array( $core ) ) {
        $core = array();
    }
    $merged = array_merge( $core, pt_wuclean_get_unused_product_tag_orphaned_items() );
    return pt_wuclean_normalize_orphaned_detect_items( $merged );
}

/**
 * Neutralize spreadsheet formula injection for CSV cells (leading =, +, -, @, etc.).
 *
 * @param mixed $value Cell value.
 * @return string
 */
function pt_wuclean_csv_escape_field(  $value  ) {
    $s = ( is_scalar( $value ) ? (string) $value : '' );
    if ( '' !== $s && preg_match( '/^[=+\\-@\\t\\r\\n\\x0B]/', $s ) ) {
        return "\t" . $s;
    }
    return $s;
}

/**
 * Export product issues to CSV
 */
function pt_wuclean_export_product_issues() {
    // Clear any output buffers and prevent any HTML output.
    if ( ob_get_level() ) {
        ob_end_clean();
    }
    // Prevent WordPress from outputting anything.
    remove_all_actions( 'wp_head' );
    remove_all_actions( 'wp_footer' );
    remove_all_actions( 'admin_head' );
    remove_all_actions( 'admin_footer' );
    $product_issues = get_transient( 'pt_wuclean_product_issues' );
    $product_issues = ( $product_issues ? $product_issues : array() );
    if ( empty( $product_issues ) ) {
        wp_die( esc_html__( 'No product issues to export.', 'wuclean-database-cleaner-for-woocommerce' ) );
    }
    // Set headers for Excel-compatible CSV download.
    header( 'Content-Type: text/csv; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename="product-issues-' . gmdate( 'Y-m-d-H-i-s' ) . '.csv"' );
    header( 'Cache-Control: no-cache, must-revalidate' );
    header( 'Expires: Sat, 26 Jul 1997 05:00:00 GMT' );
    // Create output stream.
    $output = fopen( 'php://output', 'w' );
    // Add BOM for UTF-8 (helps Excel recognize encoding).
    fprintf( $output, chr( 0xef ) . chr( 0xbb ) . chr( 0xbf ) );
    // Excel-compatible headers (matching the table display).
    $headers = array(
        'ID',
        'Title',
        'Category',
        'Type',
        'Price',
        'Image',
        'Stock',
        'Variants',
        'Parent',
        'SKU',
        'Description'
    );
    fputcsv( $output, array_map( 'pt_wuclean_csv_escape_field', $headers ) );
    // Add data rows (matching table format).
    foreach ( $product_issues as $issue ) {
        // Strip HTML tags from text fields to ensure plain text output.
        $clean_title = wp_strip_all_tags( $issue['title'] );
        $row = array(
            $issue['id'],
            $clean_title,
            ( $issue['missing_category'] ? 'X' : '' ),
            ( $issue['missing_type'] ? 'X' : '' ),
            ( $issue['missing_price'] ? 'X' : '' ),
            ( $issue['missing_image'] ? 'X' : '' ),
            ( $issue['missing_stock'] ? 'X' : '' ),
            ( $issue['missing_variants'] ? 'X' : '' ),
            ( $issue['missing_parent'] ? 'X' : '' ),
            ( $issue['missing_sku'] ? 'X' : '' ),
            ( $issue['missing_description'] ? 'X' : '' )
        );
        fputcsv( $output, array_map( 'pt_wuclean_csv_escape_field', $row ) );
    }
    // php://output stream will be automatically closed on exit.
    exit;
}

/**
 * Handle export product issues via admin action
 */
function pt_wuclean_handle_export_product_issues() {
    pt_wuclean_check_ajax_permissions();
    if ( !isset( $_POST['pt_wuclean_product_issues_nonce'] ) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pt_wuclean_product_issues_nonce'] ) ), 'pt_wuclean_product_issues' ) ) {
        wp_die( esc_html__( 'Security check failed. Please reload the page and try exporting again.', 'wuclean-database-cleaner-for-woocommerce' ), esc_html__( 'Export unavailable', 'wuclean-database-cleaner-for-woocommerce' ), array(
            'response' => 403,
        ) );
    }
    pt_wuclean_export_product_issues();
}

/**
 * Process a single batch of product search.
 *
 * @param int $batch_number Batch index (0-based).
 * @param int $batch_size   Number of products per batch.
 */
function pt_wuclean_process_product_search_batch(  $batch_number, $batch_size  ) {
    global $wpdb;
    // Set execution time limit to prevent timeouts.
    // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged, WordPress.PHP.NoSilencedErrors.Discouraged -- set_time_limit may be disabled; @ fails gracefully.
    @set_time_limit( 60 );
    // 60 seconds - conservative limit for shared hosting compatibility.
    // NOTE: We do NOT flush cache here as it would wipe batch transient data.
    // Product-specific cache is cleared in the loop below for each product.
    $offset = $batch_number * $batch_size;
    // Update progress with current batch info.
    $progress_data = get_option( 'pt_wuclean_product_search_progress', array() );
    // translators: %1$d: current batch number, %2$d: total batches.
    $progress_data['message'] = sprintf( __( 'Processing batch %1$d of %2$d...', 'wuclean-database-cleaner-for-woocommerce' ), $batch_number + 1, $progress_data['total_batches'] ?? 1 );
    $progress_data['last_activity'] = time();
    // Track last activity time.
    pt_wuclean_update_progress( 'pt_wuclean_product_search_progress', $progress_data );
    // Get batch of products.
    $products = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_title, post_name, post_type \n         FROM {$wpdb->posts} \n         WHERE post_type IN ('product', 'product_variation') \n         AND post_status NOT IN ('auto-draft', 'trash')\n         ORDER BY ID ASC\n         LIMIT %d OFFSET %d", $batch_size, $offset ) );
    // Start with empty array for this batch (results are stored in batch-specific transients).
    $product_issues = array();
    $batch_issues = 0;
    foreach ( $products as $product ) {
        $product_id = $product->ID;
        $product_title = $product->post_title;
        $product_name = $product->post_name;
        $product_type = $product->post_type;
        // Clear all caches for this product to ensure completely fresh data.
        pt_wuclean_clear_product_cache( $product_id );
        // Memory optimization: Clear object cache periodically.
        if ( 0 === $batch_issues % 5 ) {
            if ( function_exists( 'gc_collect_cycles' ) ) {
                gc_collect_cycles();
            }
        }
        // Check for all product issues using helper function.
        $issues = pt_wuclean_check_product_issues(
            $product_id,
            $product_title,
            $product_type,
            $product_name
        );
        // Only add to issues if there are problems.
        if ( array_sum( array_slice( $issues, 2 ) ) > 0 ) {
            $product_issues[] = $issues;
            ++$batch_issues;
        }
    }
    // Update progress data.
    $progress_data = get_option( 'pt_wuclean_product_search_progress', array() );
    // Ensure values are initialized (safeguard against corrupted data).
    $progress_data['processed'] = ( isset( $progress_data['processed'] ) ? (int) $progress_data['processed'] : 0 );
    $progress_data['issues_found'] = ( isset( $progress_data['issues_found'] ) ? (int) $progress_data['issues_found'] : 0 );
    $progress_data['processed'] += count( $products );
    $progress_data['issues_found'] += $batch_issues;
    $progress_data['current_batch'] = $batch_number + 1;
    $progress_data['last_activity'] = time();
    // Update last activity time.
    $progress_data['end_time'] = time();
    $progress_data['duration'] = $progress_data['end_time'] - ($progress_data['start_time'] ?? $progress_data['end_time']);
    // Store batch results FIRST, before checking if complete.
    // This ensures transients are stored in the database, not just object cache.
    $batch_key = 'pt_wuclean_product_issues_batch_' . $batch_number;
    set_transient( $batch_key, $product_issues, 2 * HOUR_IN_SECONDS );
    // Update progress message and check if this is the last batch.
    if ( ($progress_data['current_batch'] ?? 0) >= ($progress_data['total_batches'] ?? 1) ) {
        // This is the last batch - aggregate all results NOW.
        $final_issues = array();
        $total_batches = $progress_data['total_batches'] ?? 0;
        for ($i = 0; $i < $total_batches; $i++) {
            $batch_key = 'pt_wuclean_product_issues_batch_' . $i;
            $batch_issues = get_transient( $batch_key );
            $batch_issues = ( $batch_issues ? $batch_issues : array() );
            if ( !empty( $batch_issues ) ) {
                $final_issues = array_merge( $final_issues, $batch_issues );
                delete_transient( $batch_key );
            }
        }
        // Always store results, even if empty, to clear old data.
        pt_wuclean_store_data_24h( 'pt_wuclean_product_issues', $final_issues );
        // Update scan timestamps for all products (manual scan scanned everything).
        // This prevents scan from thinking products still need scanning.
        $all_product_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} \n             WHERE post_type IN ('product', 'product_variation') \n             AND post_status NOT IN ('auto-draft', 'trash')" );
        $scan_timestamps = get_option( 'pt_wuclean_product_scan_timestamps', array() );
        $current_time = time();
        foreach ( $all_product_ids as $product_id ) {
            $scan_timestamps[$product_id] = $current_time;
        }
        update_option( 'pt_wuclean_product_scan_timestamps', $scan_timestamps, false );
        // Update last_scan_check to now.
        update_option( 'pt_wuclean_last_scan_check', $current_time, false );
        // Clear behind schedule warning since manual scan completed.
        delete_option( 'pt_wuclean_scan_behind_info' );
        $progress_data['status'] = 'completed';
        $final_issues_count = count( $final_issues );
        // Update issues_found to final count for accurate redirect logic.
        $progress_data['issues_found'] = $final_issues_count;
        // Include stored data count for redirect logic (may include issues from previous scans).
        $stored_issues = get_transient( 'pt_wuclean_product_issues' );
        $stored_issues = ( $stored_issues ? $stored_issues : get_option( 'pt_wuclean_product_issues', array() ) );
        $progress_data['stored_data_count'] = ( is_array( $stored_issues ) ? count( $stored_issues ) : 0 );
        // translators: %d: number of issues found.
        $progress_data['message'] = sprintf( __( 'Search completed! Found %d issues.', 'wuclean-database-cleaner-for-woocommerce' ), $final_issues_count );
    } else {
        // More batches to process.
        // translators: %1$d: number of processed items, %2$d: number of issues found.
        $progress_data['message'] = sprintf( __( 'Processed %1$d items, found %2$d issues...', 'wuclean-database-cleaner-for-woocommerce' ), $progress_data['processed'] ?? 0, $progress_data['issues_found'] ?? 0 );
    }
    pt_wuclean_update_progress( 'pt_wuclean_product_search_progress', $progress_data );
    // Memory cleanup - only garbage collection, NO cache flushing.
    // Cache flush would wipe our transient data.
    if ( function_exists( 'gc_collect_cycles' ) ) {
        gc_collect_cycles();
    }
}

/**
 * Helper: Clear all cache for a product
 *
 * @param int $product_id The product ID to clear cache for.
 */
function pt_wuclean_clear_product_cache(  $product_id  ) {
    clean_post_cache( $product_id );
    wp_cache_delete( $product_id, 'post_meta' );
    wp_cache_delete( $product_id, 'posts' );
    wp_cache_delete( 'post_meta_' . $product_id, 'post_meta' );
    wp_cache_delete( 'meta_' . $product_id, 'post_meta' );
    wp_cache_delete( $product_id, 'post_tag_relationships' );
    wp_cache_delete( $product_id, 'post_category_relationships' );
    wp_cache_delete( $product_id, 'term_relationships' );
    if ( function_exists( 'wc_delete_product_transients' ) ) {
        wc_delete_product_transients( $product_id );
    }
    wp_cache_set( 'last_changed', microtime(), 'posts' );
}

/**
 * Helper: Check a product for common issues
 *
 * @param int    $product_id    Product ID.
 * @param string $product_title Product title.
 * @param string $product_type  Product post_type.
 * @param string $product_name  Product post_name (optional, for orphaned variant fallback).
 * @return array Array of issues found.
 */
function pt_wuclean_check_product_issues(
    $product_id,
    $product_title,
    $product_type,
    $product_name = ''
) {
    global $wpdb;
    $issues = array(
        'id'                  => $product_id,
        'title'               => $product_title,
        'type'                => $product_type,
        'missing_category'    => false,
        'missing_type'        => false,
        'missing_price'       => false,
        'missing_image'       => false,
        'missing_stock'       => false,
        'missing_variants'    => false,
        'missing_parent'      => false,
        'missing_sku'         => false,
        'missing_description' => false,
    );
    // Determine if this is a variation (variations need different checks).
    $is_variation = 'product_variation' === $product_type;
    // Check for missing category - SKIP for variations (they inherit from parent).
    if ( !$is_variation ) {
        $category_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->term_relationships} tr \n                 INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id \n                 WHERE tr.object_id = %d AND tt.taxonomy = 'product_cat'", $product_id ) );
        if ( empty( $category_count ) ) {
            $issues['missing_category'] = true;
        }
    }
    // Check for missing product type - SKIP for variations (they use post_type, not taxonomy).
    $product_type_slug = '';
    if ( !$is_variation ) {
        $product_type_terms = wp_get_object_terms( $product_id, 'product_type', array(
            'fields' => 'slugs',
        ) );
        if ( is_wp_error( $product_type_terms ) || empty( $product_type_terms ) ) {
            $issues['missing_type'] = true;
        }
        $product_type_slug = ( !empty( $product_type_terms ) && !is_wp_error( $product_type_terms ) ? $product_type_terms[0] : '' );
    }
    // Check for missing price - use direct database query to bypass cache.
    $price = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_price' LIMIT 1", $product_id ) );
    $regular_price = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_regular_price' LIMIT 1", $product_id ) );
    if ( empty( $price ) && empty( $regular_price ) ) {
        $issues['missing_price'] = true;
    }
    // Check for missing image - use direct database query to bypass cache.
    $image_id = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_thumbnail_id' LIMIT 1", $product_id ) );
    if ( empty( $image_id ) ) {
        $issues['missing_image'] = true;
    }
    // Check for missing stock - use direct database query to bypass cache.
    if ( 'virtual' !== $product_type_slug && 'downloadable' !== $product_type_slug ) {
        $stock_status = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_stock_status' LIMIT 1", $product_id ) );
        if ( 'outofstock' === $stock_status ) {
            $issues['missing_stock'] = true;
        }
    }
    // Check for missing variants (for variable products) - use direct query for efficiency.
    if ( 'variable' === $product_type_slug ) {
        $variants_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} \n                 WHERE post_parent = %d AND post_type = 'product_variation' AND post_status != 'trash'", $product_id ) );
        if ( empty( $variants_count ) ) {
            $issues['missing_variants'] = true;
        }
    }
    // Check for missing parent (for variations) - use post_parent from wp_posts table.
    if ( $is_variation ) {
        $parent_id = wp_get_post_parent_id( $product_id );
        if ( !$parent_id || false === get_post_status( $parent_id ) ) {
            $issues['missing_parent'] = true;
            // For orphaned variants, use post_name as title if post_title is empty.
            if ( $issues['missing_parent'] && empty( $product_title ) && !empty( $product_name ) ) {
                $issues['title'] = $product_name;
            }
        }
    }
    // Check for missing SKU - SKIP for variations (they often inherit from parent or don't need one).
    if ( !$is_variation ) {
        $sku = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_sku' LIMIT 1", $product_id ) );
        if ( empty( $sku ) ) {
            $issues['missing_sku'] = true;
        }
    }
    // Check for missing description - SKIP for variations (they inherit from parent).
    if ( !$is_variation ) {
        $long_description = get_post_field( 'post_content', $product_id );
        $short_description = get_post_field( 'post_excerpt', $product_id );
        if ( empty( trim( $long_description ?? '' ) ) && empty( trim( $short_description ?? '' ) ) ) {
            $issues['missing_description'] = true;
        }
    }
    return $issues;
}

/**
 * Helper: Clean up scan timestamps for deleted products.
 * Called when products are permanently deleted.
 *
 * @param int $post_id Post ID.
 */
function pt_wuclean_cleanup_product_scan_timestamp(  $post_id  ) {
    $post_type = get_post_type( $post_id );
    if ( in_array( $post_type, array('product', 'product_variation'), true ) ) {
        $scan_timestamps = get_option( 'pt_wuclean_product_scan_timestamps', array() );
        if ( isset( $scan_timestamps[$post_id] ) ) {
            unset($scan_timestamps[$post_id]);
            update_option( 'pt_wuclean_product_scan_timestamps', $scan_timestamps, false );
        }
    }
}

add_action( 'before_delete_post', 'pt_wuclean_cleanup_product_scan_timestamp' );
/**
 * Helper: Reset product scan timestamps (for manual reset if needed)
 * Can be called from settings or debug panel
 */
function pt_wuclean_reset_product_scan_timestamps() {
    delete_option( 'pt_wuclean_product_scan_timestamps' );
    delete_option( 'pt_wuclean_last_scan_check' );
    delete_option( 'pt_wuclean_scheduled_product_scan_progress' );
    // Clean up old progress tracking (legacy).
    delete_transient( 'pt_wuclean_scheduled_product_scan_progress' );
    // Clean up new background batch progress.
    delete_option( 'pt_wuclean_scan_behind_info' );
    // Clean up behind schedule info.
    delete_transient( 'pt_wuclean_scan_behind_schedule' );
}

/**
 * AJAX handler to start unused attributes search.
 */
function pt_wuclean_ajax_start_unused_attributes_search() {
    pt_wuclean_check_ajax_permissions();
    if ( !isset( $_POST['nonce'] ) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'pt_wuclean_unused_attributes_search' ) ) {
        wp_send_json_error( array(
            'message' => __( 'Invalid nonce', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
        return;
    }
    // Clear existing results (both transient and option).
    delete_transient( 'pt_wuclean_unused_attributes' );
    delete_option( 'pt_wuclean_unused_attributes' );
    // Clear old progress data from previous runs.
    delete_option( 'pt_wuclean_unused_attributes_search_progress' );
    // Clear temp accumulator data from previous runs.
    delete_option( 'pt_wuclean_unused_attributes_temp' );
    // Flush cache to ensure deletions take effect.
    wp_cache_delete( 'pt_wuclean_unused_attributes_search_progress', 'options' );
    wp_cache_delete( 'pt_wuclean_unused_attributes_temp', 'options' );
    // Get list of attribute taxonomies for processing.
    global $wpdb;
    $global_attributes = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT tt.taxonomy\n             FROM {$wpdb->term_taxonomy} tt\n             WHERE tt.taxonomy LIKE %s\n             ORDER BY tt.taxonomy", 'pa_%' ) );
    // Only process registered taxonomies (skip orphaned pa_* rows) to avoid PHP warnings.
    $global_attributes = array_values( array_filter( $global_attributes, function ( $row ) {
        return (bool) get_taxonomy( $row->taxonomy );
    } ) );
    // Update term counts for all product attribute taxonomies BEFORE checking.
    // This ensures we get accurate counts (WordPress counts can get stale).
    foreach ( $global_attributes as $attr_taxonomy ) {
        // Get all term IDs for this taxonomy.
        $term_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_id FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s", $attr_taxonomy->taxonomy ) );
        if ( !empty( $term_ids ) ) {
            // Update counts for this taxonomy.
            wp_update_term_count_now( $term_ids, $attr_taxonomy->taxonomy );
        }
    }
    // Initialize progress data with multi-step state tracking.
    $progress_data = array(
        'status'                  => 'running',
        'current_taxonomy_index'  => 0,
        'total_taxonomies'        => count( $global_attributes ),
        'taxonomies'              => array_column( $global_attributes, 'taxonomy' ),
        'processed'               => 0,
        'total_items'             => 0,
        'unused_attributes_found' => 0,
        'start_time'              => time(),
        'last_activity'           => time(),
        'message'                 => __( 'Initializing unused attributes search...', 'wuclean-database-cleaner-for-woocommerce' ),
    );
    update_option( 'pt_wuclean_unused_attributes_search_progress', $progress_data, false );
    wp_send_json_success( array(
        'message'  => __( 'Unused attributes search started successfully.', 'wuclean-database-cleaner-for-woocommerce' ),
        'progress' => $progress_data,
    ) );
}

/**
 * AJAX handler to process next unused attributes batch.
 * This processes one taxonomy at a time.
 */
function pt_wuclean_ajax_process_unused_attributes_batch() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_unused_attributes_search', 'nonce' );
    // Get current progress.
    $progress_data = get_option( 'pt_wuclean_unused_attributes_search_progress', array() );
    if ( empty( $progress_data ) ) {
        wp_send_json_error( array(
            'message' => __( 'No unused attributes search found.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
    // Check if search is already completed.
    if ( 'completed' === $progress_data['status'] ) {
        wp_send_json_success( array(
            'message'   => __( 'Unused attributes search completed.', 'wuclean-database-cleaner-for-woocommerce' ),
            'completed' => true,
            'progress'  => $progress_data,
        ) );
    }
    // Check if status is running.
    if ( 'running' !== ($progress_data['status'] ?? '') ) {
        wp_send_json_error( array(
            'message' => __( 'Unused attributes search is not running.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
    // Process the next taxonomy.
    pt_wuclean_process_unused_attributes_batch();
    // Get updated progress (may have changed to 'completed').
    $progress_data = get_option( 'pt_wuclean_unused_attributes_search_progress', array() );
    // Check if completed after processing.
    $is_completed = ($progress_data['status'] ?? '') === 'completed';
    wp_send_json_success( array(
        'message'   => ( $is_completed ? __( 'Unused attributes search completed.', 'wuclean-database-cleaner-for-woocommerce' ) : $progress_data['message'] ?? __( 'Processing...', 'wuclean-database-cleaner-for-woocommerce' ) ),
        'completed' => $is_completed,
        'progress'  => $progress_data,
    ) );
}

/**
 * Process one taxonomy of unused attributes search (batch processing).
 * Processes one attribute taxonomy at a time for shared hosting compatibility.
 */
function pt_wuclean_process_unused_attributes_batch() {
    global $wpdb;
    // Set execution time limit to prevent timeouts.
    // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged, WordPress.PHP.NoSilencedErrors.Discouraged -- set_time_limit may be disabled; @ fails gracefully.
    @set_time_limit( 60 );
    // 60 seconds - conservative limit for shared hosting compatibility.
    // Get current progress.
    $progress_data = get_option( 'pt_wuclean_unused_attributes_search_progress', array() );
    $current_taxonomy_index = $progress_data['current_taxonomy_index'] ?? 0;
    $taxonomies = $progress_data['taxonomies'] ?? array();
    $total_taxonomies = count( $taxonomies );
    // Get or initialize unused attributes accumulator (ensure it's an array).
    $unused_attributes = get_option( 'pt_wuclean_unused_attributes_temp', array() );
    if ( !is_array( $unused_attributes ) ) {
        $unused_attributes = array();
    }
    $progress_data['last_activity'] = time();
    // Check if we're done with all taxonomies.
    if ( $current_taxonomy_index >= $total_taxonomies ) {
        // Final step: Store all results.
        $progress_data['message'] = __( 'Finalizing results...', 'wuclean-database-cleaner-for-woocommerce' );
        update_option( 'pt_wuclean_unused_attributes_search_progress', $progress_data, false );
        // Store final results.
        pt_wuclean_store_data_24h( 'pt_wuclean_unused_attributes', $unused_attributes );
        // Clean up temp data.
        delete_option( 'pt_wuclean_unused_attributes_temp' );
        // Mark as completed.
        $progress_data['status'] = 'completed';
        $progress_data['message'] = sprintf( 
            // translators: %d: number of unused attributes.
            __( 'Search completed! Found %d unused attributes.', 'wuclean-database-cleaner-for-woocommerce' ),
            count( $unused_attributes )
         );
        update_option( 'pt_wuclean_unused_attributes_search_progress', $progress_data, false );
        return;
    }
    // Get current taxonomy to process.
    $current_taxonomy = $taxonomies[$current_taxonomy_index];
    // translators: %s: taxonomy name.
    $progress_data['message'] = sprintf( __( 'Processing %s...', 'wuclean-database-cleaner-for-woocommerce' ), $current_taxonomy );
    update_option( 'pt_wuclean_unused_attributes_search_progress', $progress_data, false );
    // Get all terms for this taxonomy with their usage counts.
    $terms = $wpdb->get_results( $wpdb->prepare( "SELECT t.term_id, t.name, t.slug, tt.term_taxonomy_id, tt.count\n         FROM {$wpdb->terms} t\n         INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id\n         WHERE tt.taxonomy = %s\n         ORDER BY t.name", $current_taxonomy ) );
    // Find terms with count = 0 (unused).
    foreach ( $terms as $term ) {
        $progress_data['processed'] = ($progress_data['processed'] ?? 0) + 1;
        if ( 0 === (int) $term->count ) {
            // Get the attribute name (remove 'pa_' prefix).
            $attribute_name = str_replace( 'pa_', '', $current_taxonomy );
            $attribute_name = ucfirst( str_replace( '_', ' ', $attribute_name ) );
            $unused_attributes[] = array(
                'type'             => 'unused_global_attribute',
                'id'               => $term->term_id,
                'term_taxonomy_id' => $term->term_taxonomy_id,
                'attribute_name'   => $attribute_name,
                'attribute_slug'   => $current_taxonomy,
                'term_name'        => $term->name,
                'term_slug'        => $term->slug,
                'usage_count'      => $term->count,
                'description'      => sprintf(
                    // translators: %1$s: taxonomy name, %2$s: term name, %3$d: usage count.
                    __( 'Unused global attribute: %1$s - %2$s (Usage: %3$d)', 'wuclean-database-cleaner-for-woocommerce' ),
                    $attribute_name,
                    $term->name,
                    $term->count
                ),
            );
        }
    }
    // Update counts.
    $progress_data['unused_attributes_found'] = count( $unused_attributes );
    // Move to next taxonomy.
    $progress_data['current_taxonomy_index'] = $current_taxonomy_index + 1;
    // Update message.
    if ( $progress_data['current_taxonomy_index'] >= $total_taxonomies ) {
        $progress_data['message'] = sprintf( 
            // translators: %1$d: number of taxonomies, %2$d: number of unused attributes.
            __( 'Processed all %1$d attribute taxonomies, found %2$d unused...', 'wuclean-database-cleaner-for-woocommerce' ),
            $total_taxonomies,
            count( $unused_attributes )
         );
    } else {
        $progress_data['message'] = sprintf(
            // translators: %1$d: current number, %2$d: total taxonomies, %3$d: number of unused attributes.
            __( 'Processed %1$d/%2$d attribute taxonomies, found %3$d unused...', 'wuclean-database-cleaner-for-woocommerce' ),
            $progress_data['current_taxonomy_index'],
            $total_taxonomies,
            count( $unused_attributes )
        );
    }
    // Save accumulated data and progress.
    update_option( 'pt_wuclean_unused_attributes_temp', $unused_attributes, false );
    update_option( 'pt_wuclean_unused_attributes_search_progress', $progress_data, false );
    // Memory cleanup.
    if ( function_exists( 'gc_collect_cycles' ) ) {
        gc_collect_cycles();
    }
}

/**
 * AJAX handler to get unused attributes search progress.
 */
function pt_wuclean_ajax_get_unused_attributes_search_progress() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_unused_attributes_search', 'nonce' );
    $progress_data = get_option( 'pt_wuclean_unused_attributes_search_progress', array() );
    if ( empty( $progress_data ) ) {
        wp_send_json_error( array(
            'message' => __( 'No search in progress', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
    wp_send_json_success( $progress_data );
}

/**
 * AJAX handler to stop unused attributes search.
 */
function pt_wuclean_ajax_stop_unused_attributes_search() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_unused_attributes_search', 'nonce' );
    // Clear the progress data and temp data.
    delete_option( 'pt_wuclean_unused_attributes_search_progress' );
    delete_option( 'pt_wuclean_unused_attributes_temp' );
    wp_send_json_success( array(
        'message' => __( 'Unused attributes search stopped successfully.', 'wuclean-database-cleaner-for-woocommerce' ),
    ) );
}

/**
 * AJAX handler to clear unused attributes search progress.
 */
function pt_wuclean_ajax_clear_unused_attributes_search_progress() {
    pt_wuclean_check_ajax_permissions();
    check_ajax_referer( 'pt_wuclean_unused_attributes_search', 'nonce' );
    delete_option( 'pt_wuclean_unused_attributes_search_progress' );
    wp_send_json_success( array(
        'message' => __( 'Progress cleared', 'wuclean-database-cleaner-for-woocommerce' ),
    ) );
}

/**
 * AJAX handler to recheck a single product for issues
 */
function pt_wuclean_ajax_recheck_single_product() {
    ob_clean();
    if ( !isset( $_POST['nonce'] ) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'pt_wuclean_recheck_product' ) ) {
        wp_send_json_error( __( 'Security check failed', 'wuclean-database-cleaner-for-woocommerce' ) );
        return;
    }
    if ( !current_user_can( pt_wuclean_get_required_capability() ) ) {
        wp_send_json_error( __( 'Insufficient permissions', 'wuclean-database-cleaner-for-woocommerce' ) );
        return;
    }
    $product_id = ( isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0 );
    if ( !$product_id ) {
        wp_send_json_error( __( 'Invalid product ID', 'wuclean-database-cleaner-for-woocommerce' ) );
        return;
    }
    global $wpdb;
    // Get product details.
    $product = $wpdb->get_row( $wpdb->prepare( "SELECT ID, post_title, post_type \n         FROM {$wpdb->posts} \n         WHERE ID = %d", $product_id ) );
    if ( !$product ) {
        wp_send_json_error( __( 'Product not found', 'wuclean-database-cleaner-for-woocommerce' ) );
        return;
    }
    $product_title = $product->post_title;
    $product_type = $product->post_type;
    // Clear all caches for this product to ensure completely fresh data.
    pt_wuclean_clear_product_cache( $product_id );
    // Check for all product issues using helper function.
    $issues = pt_wuclean_check_product_issues(
        $product_id,
        $product_title,
        $product_type,
        ''
    );
    // Count how many issues found.
    $has_issues = $issues['missing_category'] || $issues['missing_type'] || $issues['missing_price'] || $issues['missing_image'] || $issues['missing_stock'] || $issues['missing_variants'] || $issues['missing_parent'] || $issues['missing_sku'] || $issues['missing_description'];
    if ( !$has_issues ) {
        // No issues found - remove from stored issues list.
        $product_issues = get_transient( 'pt_wuclean_product_issues' );
        $product_issues = ( $product_issues ? $product_issues : array() );
        if ( empty( $product_issues ) ) {
            $product_issues = get_option( 'pt_wuclean_product_issues', array() );
        }
        $product_issues = array_filter( $product_issues, function ( $issue ) use($product_id) {
            return $product_id !== $issue['id'];
        } );
        $product_issues = array_values( $product_issues );
        pt_wuclean_store_data_24h( 'pt_wuclean_product_issues', $product_issues );
        wp_send_json_success( array(
            'has_issues' => false,
            'message'    => __( 'No issues found! Product removed from list.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    } else {
        // Issues still exist - update the stored data.
        $product_issues = get_transient( 'pt_wuclean_product_issues' );
        $product_issues = ( $product_issues ? $product_issues : array() );
        if ( empty( $product_issues ) ) {
            $product_issues = get_option( 'pt_wuclean_product_issues', array() );
        }
        // Update or add this product's issues.
        $found = false;
        foreach ( $product_issues as $key => $issue ) {
            if ( $issue['id'] === $product_id ) {
                $product_issues[$key] = $issues;
                $found = true;
                break;
            }
        }
        if ( !$found ) {
            $product_issues[] = $issues;
        }
        pt_wuclean_store_data_24h( 'pt_wuclean_product_issues', $product_issues );
        wp_send_json_success( array(
            'has_issues' => true,
            'issues'     => $issues,
            'message'    => __( 'Product rechecked. Issues still exist.', 'wuclean-database-cleaner-for-woocommerce' ),
        ) );
    }
}

// Product Issues AJAX handlers.
add_action( 'wp_ajax_pt_wuclean_start_product_search', 'pt_wuclean_ajax_start_product_search' );
add_action( 'wp_ajax_pt_wuclean_process_product_batch', 'pt_wuclean_ajax_process_product_batch' );
add_action( 'wp_ajax_pt_wuclean_get_product_search_progress', 'pt_wuclean_ajax_get_product_search_progress' );
add_action( 'wp_ajax_pt_wuclean_stop_product_search', 'pt_wuclean_ajax_stop_product_search' );
add_action( 'wp_ajax_pt_wuclean_clear_search_progress', 'pt_wuclean_ajax_clear_search_progress' );
add_action( 'wp_ajax_pt_wuclean_recheck_single_product', 'pt_wuclean_ajax_recheck_single_product' );
// Hook for AJAX orphaned data search actions.
add_action( 'wp_ajax_pt_wuclean_start_orphaned_search', 'pt_wuclean_ajax_start_orphaned_search' );
add_action( 'wp_ajax_pt_wuclean_process_orphaned_batch', 'pt_wuclean_ajax_process_orphaned_batch' );
add_action( 'wp_ajax_pt_wuclean_get_orphaned_search_progress', 'pt_wuclean_ajax_get_orphaned_search_progress' );
add_action( 'wp_ajax_pt_wuclean_stop_orphaned_search', 'pt_wuclean_ajax_stop_orphaned_search' );
add_action( 'wp_ajax_pt_wuclean_clear_orphaned_search_progress', 'pt_wuclean_ajax_clear_orphaned_search_progress' );
// Hook for AJAX unused attributes actions.
add_action( 'wp_ajax_pt_wuclean_start_unused_attributes_search', 'pt_wuclean_ajax_start_unused_attributes_search' );
add_action( 'wp_ajax_pt_wuclean_process_unused_attributes_batch', 'pt_wuclean_ajax_process_unused_attributes_batch' );
add_action( 'wp_ajax_pt_wuclean_get_unused_attributes_search_progress', 'pt_wuclean_ajax_get_unused_attributes_search_progress' );
add_action( 'wp_ajax_pt_wuclean_stop_unused_attributes_search', 'pt_wuclean_ajax_stop_unused_attributes_search' );
add_action( 'wp_ajax_pt_wuclean_clear_unused_attributes_search_progress', 'pt_wuclean_ajax_clear_unused_attributes_search_progress' );
/**
 * Helper function to store data in both transient and option with consistent expiry.
 *
 * @param string $key    Option/transient key.
 * @param mixed  $data   Data to store.
 * @param int    $expiry Expiry in seconds. Default 24 hours.
 */
function pt_wuclean_store_data(  $key, $data, $expiry = 24 * HOUR_IN_SECONDS  ) {
    $key = ( is_string( $key ) ? $key : '' );
    if ( '' === $key || 0 !== strpos( $key, 'pt_wuclean_' ) ) {
        return;
    }
    // Safety: prevent very large arrays from being stored as serialized options (memory/DB bloat).
    if ( is_array( $data ) && '' !== $key ) {
        /**
         * Filter which data keys should be capped when persisted.
         *
         * @param string[] $capped_keys List of transient/option keys that should be capped.
         */
        $capped_keys = apply_filters( 'pt_wuclean_store_data_capped_keys', array('pt_wuclean_product_issues', 'pt_wuclean_orphaned_data', 'pt_wuclean_unused_attributes') );
        if ( in_array( $key, $capped_keys, true ) ) {
            /**
             * Filter maximum number of items to store for capped keys.
             *
             * @param int    $max_items Maximum items to store (0 = no cap).
             * @param string $key       Option/transient key.
             */
            $max_items = apply_filters( 'pt_wuclean_store_data_max_items', 10000, $key );
            $max_items = intval( $max_items );
            if ( $max_items > 0 && count( $data ) > $max_items ) {
                $data = array_slice( $data, 0, $max_items );
            }
            // Store a lightweight count alongside the data so admin UI can avoid loading large arrays.
            $count_key = $key . '_count';
            if ( 0 !== strpos( $count_key, 'pt_wuclean_' ) ) {
                $count_key = 'pt_wuclean_' . ltrim( $count_key, '_' );
            }
            update_option( $count_key, count( $data ), false );
        }
    }
    set_transient( $key, $data, $expiry );
    update_option( $key, $data, false );
}

/**
 * Helper function to store data in both transient and option with default 24-hour expiry.
 *
 * @param string $key  Option/transient key.
 * @param mixed  $data Data to store.
 */
function pt_wuclean_store_data_24h(  $key, $data  ) {
    pt_wuclean_store_data( $key, $data, 24 * HOUR_IN_SECONDS );
}

/**
 * Helper function to update progress data with consistent structure.
 *
 * @param string $option_key Option key to store under.
 * @param mixed  $data       Progress data to store.
 */
function pt_wuclean_update_progress(  $option_key, $data  ) {
    $option_key = ( is_string( $option_key ) ? $option_key : '' );
    if ( '' === $option_key || 0 !== strpos( $option_key, 'pt_wuclean_' ) ) {
        return;
    }
    update_option( $option_key, $data, false );
}

/**
 * Helper function to check user permissions and die if insufficient
 */
function pt_wuclean_check_permissions() {
    if ( !current_user_can( pt_wuclean_get_required_capability() ) ) {
        wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wuclean-database-cleaner-for-woocommerce' ) );
    }
}

/**
 * Helper function to check user permissions for AJAX calls and die if insufficient
 */
function pt_wuclean_check_ajax_permissions() {
    if ( !current_user_can( pt_wuclean_get_required_capability() ) ) {
        wp_die( esc_html__( 'Insufficient permissions', 'wuclean-database-cleaner-for-woocommerce' ) );
    }
}
