<?php
/**
 * Products admin page – renders WP_List_Table, handles AJAX and bulk actions.
 *
 * AJAX actions:
 *   skyemag_toggle_sync      – enable/disable sync for one product
 *   skyemag_save_margin      – save custom margin %, returns new eMAG price
 *   skyemag_save_min_margin  – save custom min margin %
 *   skyemag_save_stock_pct   – save custom stock %, returns new eMAG stock
 *   skyemag_save_all         – batch-save all margin + stock % inputs
 *   skyemag_bulk_action      – all bulk actions
 *
 * @package SkyEmagSync
 */

namespace SkyEmagSync\Admin;

use SkyEmagSync\History\MarginHistory;
use SkyEmagSync\Options;
use SkyEmagSync\Plugin;
use SkyEmagSync\PostMeta;
use SkyEmagSync\Api\OfferPayload;
use SkyEmagSync\Sync\Runner;

defined( 'ABSPATH' ) || exit;

class ProductsPage {

    const NONCE_ACTION = 'skyemag_products_action';

    public function register(): void {
        add_action( 'wp_ajax_skyemag_toggle_sync',      [ $this, 'ajax_toggle_sync' ] );
        add_action( 'wp_ajax_skyemag_save_margin',      [ $this, 'ajax_save_margin' ] );
        add_action( 'wp_ajax_skyemag_save_min_margin',  [ $this, 'ajax_save_min_margin' ] );
        add_action( 'wp_ajax_skyemag_save_max_margin',  [ $this, 'ajax_save_max_margin' ] );
        add_action( 'wp_ajax_skyemag_save_stock_pct',   [ $this, 'ajax_save_stock_pct' ] );
        add_action( 'wp_ajax_skyemag_save_all',       [ $this, 'ajax_save_all' ] );
        add_action( 'wp_ajax_skyemag_bulk_action',    [ $this, 'ajax_bulk_action' ] );
        add_action( 'wp_ajax_skyemag_set_emag_status', [ $this, 'ajax_set_emag_status' ] );
        add_action( 'wp_ajax_skyemag_margin_history', [ $this, 'ajax_margin_history' ] );
        add_action( 'wp_ajax_skyemag_margin_revert',  [ $this, 'ajax_margin_revert' ] );
        add_action( 'wp_ajax_skyemag_unlink_product',        [ $this, 'ajax_unlink_product' ] );
        add_action( 'wp_ajax_skyemag_fix_duplicate_mappings', [ $this, 'ajax_fix_duplicate_mappings' ] );
        add_action( 'admin_init',                    [ $this, 'handle_bulk_post' ] );
    }

    // ── AJAX: margin history (for rollback modal) ────────────────────────

    public function ajax_margin_history(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
        $this->get_product_or_fail( $product_id );

        $entries = MarginHistory::get_for_product( $product_id, 20 );
        wp_send_json_success( [
            'product_id' => $product_id,
            'entries'    => $entries,
            'total'      => MarginHistory::count_for_product( $product_id ),
        ] );
    }

    public function ajax_margin_revert(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
        $entry_id   = isset( $_POST['entry_id'] )   ? absint( wp_unslash( $_POST['entry_id'] ) )   : 0;
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
        $product    = $this->get_product_or_fail( $product_id );

        $entry = MarginHistory::get_entry( $entry_id, $product_id );
        if ( null === $entry ) {
            wp_send_json_error( [ 'message' => __( 'History entry not found or belongs to another product.', 'skyservers-product-sync-for-emag' ) ] );
        }

        // Restore the "before" value from the chosen entry. set_margin()
        // will insert a new history row via the existing action hook, so the
        // revert itself is also auditable and can be undone later.
        PostMeta::set_margin( $product_id, $entry['old_margin'] );

        $current    = PostMeta::get_margin( $product_id );
        $emag_price = OfferPayload::computed_price( $product );
        $vat_id     = PostMeta::get_vat_id( $product_id );
        $vat_rate   = ( null !== $vat_id ) ? Options::vat_rate_for_id( $vat_id ) : null;
        $emag_inc   = ( null !== $emag_price && null !== $vat_rate && $vat_rate > 0 )
                        ? round( $emag_price * ( 1 + $vat_rate ), 2 )
                        : null;

        wp_send_json_success( [
            'product_id'      => $product_id,
            'margin'          => $current,
            'emag_price'      => self::format_price( $emag_price ),
            'emag_price_inc'  => null !== $emag_inc ? number_format( $emag_inc, 2 ) : null,
            'below_min_margin' => OfferPayload::is_below_min_margin( $product ),
            'message'         => sprintf(
                /* translators: %s: restored margin value (or "(cleared)") */
                __( 'Margin reverted to %s.', 'skyservers-product-sync-for-emag' ),
                null === $current ? __( '(cleared)', 'skyservers-product-sync-for-emag' ) : number_format( (float) $current, 2 ) . '%'
            ),
        ] );
    }

    // ── Render ────────────────────────────────────────────────────────────

    public static function render(): void {
        if ( ! current_user_can( Menu::CAPABILITY ) ) {
            return;
        }

        $table = new ProductsTable();
        $table->prepare_items();
        ?>
        <div class="wrap skyemag-products-wrap">
            <h1 class="wp-heading-inline"><?php esc_html_e( 'SkyServers Sync — Products', 'skyservers-product-sync-for-emag' ); ?></h1>

            <?php self::render_notices(); ?>
            <?php self::render_duplicate_mappings_notice(); ?>

            <?php
            // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only display, pagination/filter state, no state mutation.
            $current_pp = isset( $_REQUEST['per_page'] ) && in_array( absint( wp_unslash( $_REQUEST['per_page'] ) ), [ 20, 50, 100 ], true )
                ? absint( wp_unslash( $_REQUEST['per_page'] ) ) : 25;
            $pp_base = add_query_arg( 'page', Menu::PRODUCTS_SLUG, admin_url( 'admin.php' ) );
            if ( isset( $_REQUEST['sync_filter'] ) ) {
                $pp_base = add_query_arg( 'sync_filter', sanitize_key( wp_unslash( $_REQUEST['sync_filter'] ) ), $pp_base );
            }
            // phpcs:enable WordPress.Security.NonceVerification.Recommended
            ?>
            <div style="margin-bottom:6px;">
                <?php esc_html_e( 'Per page:', 'skyservers-product-sync-for-emag' ); ?>
                <?php foreach ( [ 20, 50, 100 ] as $pp ) :
                    $url = add_query_arg( [ 'per_page' => $pp, 'paged' => 1 ], $pp_base );
                ?>
                    <?php if ( $pp === $current_pp ) : ?>
                        <strong><?php echo (int) $pp; ?></strong>
                    <?php else : ?>
                        <a href="<?php echo esc_url( $url ); ?>"><?php echo (int) $pp; ?></a>
                    <?php endif; ?>
                    <?php echo 100 !== $pp ? '|' : ''; ?>
                <?php endforeach; ?>
            </div>

            <form id="skyemag-products-form" method="get">
                <input type="hidden" name="page" value="<?php echo esc_attr( Menu::PRODUCTS_SLUG ); ?>" />
                <input type="hidden" name="per_page" value="<?php echo (int) $current_pp; ?>" />
                <?php
                // phpcs:disable WordPress.Security.NonceVerification.Recommended -- pagination/filter passthrough, no state mutation.
                if ( ! empty( $_REQUEST['sync_filter'] ) ) : ?>
                    <input type="hidden" name="sync_filter" value="<?php echo esc_attr( sanitize_key( wp_unslash( $_REQUEST['sync_filter'] ) ) ); ?>" />
                <?php endif; // phpcs:enable WordPress.Security.NonceVerification.Recommended ?>
                <?php
                $table->views();
                $table->search_box( __( 'Search products', 'skyservers-product-sync-for-emag' ), 'skyemag-product-search' );
                $table->display();
                ?>
            </form>

            <p>
                <button type="button" id="skyemag-save-all-btn" class="button button-secondary">
                    <?php esc_html_e( 'Save All Margins & Stock %', 'skyservers-product-sync-for-emag' ); ?>
                </button>
                <span id="skyemag-save-all-status" style="margin-left:8px;"></span>
            </p>
        </div>
        <input type="hidden" id="skyemag-nonce"
            value="<?php echo esc_attr( wp_create_nonce( self::NONCE_ACTION ) ); ?>" />
        <?php
    }

    // ── AJAX: toggle sync ─────────────────────────────────────────────────

    public function ajax_toggle_sync(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
        $enabled    = ! empty( $_POST['enabled'] );
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended

        if ( ! $product_id || ! wc_get_product( $product_id ) ) {
            wp_send_json_error( [ 'message' => __( 'Invalid product.', 'skyservers-product-sync-for-emag' ) ] );
        }

        PostMeta::set_sync_enabled( $product_id, $enabled );
        wp_send_json_success( [ 'product_id' => $product_id, 'enabled' => $enabled ] );
    }

    // ── AJAX: save margin ─────────────────────────────────────────────────

    public function ajax_save_margin(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
        $raw        = isset( $_POST['margin'] ) ? wp_unslash( $_POST['margin'] ) : '';
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
        $product    = $this->get_product_or_fail( $product_id );

        if ( '' === trim( (string) $raw ) ) {
            PostMeta::set_margin( $product_id, null );
        } else {
            $margin = (float) $raw;
            if ( $margin < -50 || $margin > 300 ) {
                wp_send_json_error( [ 'message' => __( 'Margin must be between -50% and 300%.', 'skyservers-product-sync-for-emag' ) ] );
            }
            PostMeta::set_margin( $product_id, $margin );
        }

        $emag_price = OfferPayload::computed_price( $product );
        $vat_id     = \SkyEmagSync\PostMeta::get_vat_id( $product_id );
        $vat_rate   = ( null !== $vat_id ) ? \SkyEmagSync\Options::vat_rate_for_id( $vat_id ) : null;
        $emag_inc   = ( null !== $emag_price && null !== $vat_rate && $vat_rate > 0 )
                        ? round( $emag_price * ( 1 + $vat_rate ), 2 )
                        : null;

        wp_send_json_success( [
            'product_id'       => $product_id,
            'emag_price'       => self::format_price( $emag_price ),
            'emag_price_inc'   => null !== $emag_inc ? number_format( $emag_inc, 2 ) : null,
            'below_min_margin' => OfferPayload::is_below_min_margin( $product ),
        ] );
    }

    // ── AJAX: save min margin % ───────────────────────────────────────────

    public function ajax_save_min_margin(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
        $raw        = isset( $_POST['min_margin'] ) ? wp_unslash( $_POST['min_margin'] ) : '';
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
        $product    = $this->get_product_or_fail( $product_id );

        if ( '' === trim( (string) $raw ) ) {
            PostMeta::set_min_margin( $product_id, null );
        } else {
            $min_margin = (float) $raw;
            if ( $min_margin < -50 || $min_margin > 300 ) {
                wp_send_json_error( [ 'message' => __( 'Min margin must be between -50% and 300%.', 'skyservers-product-sync-for-emag' ) ] );
            }
            PostMeta::set_min_margin( $product_id, $min_margin );
        }

        wp_send_json_success( [
            'product_id'       => $product_id,
            'below_min_margin' => OfferPayload::is_below_min_margin( $product ),
        ] );
    }

    // ── AJAX: save max margin % ───────────────────────────────────────────

    public function ajax_save_max_margin(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
        $raw        = isset( $_POST['max_margin'] ) ? wp_unslash( $_POST['max_margin'] ) : '';
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
        $this->get_product_or_fail( $product_id );

        if ( '' === trim( (string) $raw ) ) {
            PostMeta::set_max_margin( $product_id, null );
        } else {
            $max_margin = (float) $raw;
            if ( $max_margin < -50 || $max_margin > 1000 ) {
                wp_send_json_error( [ 'message' => __( 'Max margin must be between -50% and 1000%.', 'skyservers-product-sync-for-emag' ) ] );
            }
            PostMeta::set_max_margin( $product_id, $max_margin );
        }

        wp_send_json_success( [ 'product_id' => $product_id ] );
    }

    public function ajax_save_stock_pct(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
        $raw        = isset( $_POST['stock_percent'] ) ? wp_unslash( $_POST['stock_percent'] ) : '';
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
        $product    = $this->get_product_or_fail( $product_id );

        if ( '' === trim( (string) $raw ) ) {
            PostMeta::set_stock_percent( $product_id, null );
        } else {
            $pct = (float) $raw;
            if ( $pct < 0 || $pct > 100 ) {
                wp_send_json_error( [ 'message' => __( 'Stock % must be between 0 and 100.', 'skyservers-product-sync-for-emag' ) ] );
            }
            PostMeta::set_stock_percent( $product_id, $pct );
        }

        $emag_stock = OfferPayload::computed_stock( $product );
        wp_send_json_success( [
            'product_id' => $product_id,
            'emag_stock' => $emag_stock,
        ] );
    }

    // ── AJAX: set eMAG status (activate/deactivate) ───────────────────────

    public function ajax_set_emag_status(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
        $new_status = isset( $_POST['emag_status'] ) ? absint( wp_unslash( $_POST['emag_status'] ) ) : 1;
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended

        if ( ! in_array( $new_status, [ 0, 1 ], true ) ) {
            wp_send_json_error( [ 'message' => __( 'Invalid status value.', 'skyservers-product-sync-for-emag' ) ] );
        }

        $product = $this->get_product_or_fail( $product_id );
        $vendor_ext_id = PostMeta::get_vendor_ext_id( $product_id );

        if ( null === $vendor_ext_id ) {
            wp_send_json_error( [ 'message' => __( 'Product is not on eMAG (no vendor_ext_id).', 'skyservers-product-sync-for-emag' ) ] );
        }

        // Update local meta.
        update_post_meta( $product_id, PostMeta::EMAG_STATUS, $new_status );

        // Send to eMAG immediately.
        $client  = new \SkyEmagSync\Api\EmagClient();
        $payload = $new_status === 0
            ? [ OfferPayload::inactive( $vendor_ext_id ) ]
            : [ OfferPayload::from_product( $product ) ?? [ 'id' => $vendor_ext_id, 'status' => 1 ] ];

        $response = $client->save_offers( $payload );

        if ( $response['success'] ) {
            $label = $new_status === 0
                ? __( 'Product deactivated on eMAG.', 'skyservers-product-sync-for-emag' )
                : __( 'Product activated on eMAG.', 'skyservers-product-sync-for-emag' );
            wp_send_json_success( [
                'product_id'  => $product_id,
                'emag_status' => $new_status,
                'message'     => $label,
            ] );
        } else {
            // Revert local meta on failure.
            update_post_meta( $product_id, PostMeta::EMAG_STATUS, $new_status === 0 ? 1 : 0 );
            wp_send_json_error( [ 'message' => $response['error'] ] );
        }
    }

    // ── AJAX: save all ────────────────────────────────────────────────────

    public function ajax_save_all(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $entries = isset( $_POST['entries'] ) ? wp_unslash( $_POST['entries'] ) : [];
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
        if ( ! is_array( $entries ) ) {
            wp_send_json_error( [ 'message' => 'Invalid payload.' ] );
        }

        $saved  = 0;
        $errors = [];

        foreach ( $entries as $entry ) {
            $product_id = (int) ( $entry['product_id'] ?? 0 );
            if ( ! $product_id || ! wc_get_product( $product_id ) ) {
                continue;
            }

            // Margin
            $raw_margin = $entry['margin'] ?? null;
            if ( null !== $raw_margin ) {
                if ( '' === trim( (string) $raw_margin ) ) {
                    PostMeta::set_margin( $product_id, null );
                } else {
                    $m = (float) $raw_margin;
                    if ( $m < -50 || $m > 300 ) {
                        $errors[] = "#{$product_id}: margin out of range";
                    } else {
                        PostMeta::set_margin( $product_id, $m );
                    }
                }
            }

            // Stock %
            $raw_pct = $entry['stock_percent'] ?? null;
            if ( null !== $raw_pct ) {
                if ( '' === trim( (string) $raw_pct ) ) {
                    PostMeta::set_stock_percent( $product_id, null );
                } else {
                    $p = (float) $raw_pct;
                    if ( $p < 0 || $p > 100 ) {
                        $errors[] = "#{$product_id}: stock % out of range";
                    } else {
                        PostMeta::set_stock_percent( $product_id, $p );
                    }
                }
            }

            $saved++;
        }

        wp_send_json_success( [ 'saved' => $saved, 'errors' => $errors ] );
    }

    // ── AJAX: bulk action ─────────────────────────────────────────────────

    public function ajax_bulk_action(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- nonce verified by verify_ajax_nonce() above.
        $action       = isset( $_POST['bulk_action'] ) ? sanitize_key( wp_unslash( $_POST['bulk_action'] ) ) : '';
        $bulk_target  = isset( $_POST['bulk_target'] ) ? sanitize_key( wp_unslash( $_POST['bulk_target'] ) ) : 'selected';
        $value        = isset( $_POST['bulk_value'] ) && '' !== $_POST['bulk_value']
            ? (float) wp_unslash( $_POST['bulk_value'] )
            : null;

        if ( 'all_filtered' === $bulk_target ) {
            $search   = isset( $_POST['filter_search'] )   ? sanitize_text_field( wp_unslash( $_POST['filter_search'] ) )   : '';
            $sync_f   = isset( $_POST['filter_sync'] )     ? sanitize_key( wp_unslash( $_POST['filter_sync'] ) )            : '';
            $category = isset( $_POST['filter_category'] ) ? sanitize_title( wp_unslash( $_POST['filter_category'] ) )      : '';
            $ids      = ProductsTable::query_filtered_ids( $search, $sync_f, $category );
        } else {
            $ids = array_map( 'intval', (array) wp_unslash( $_POST['product_ids'] ?? [] ) );
        }
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended

        if ( empty( $ids ) ) {
            wp_send_json_error( [ 'message' => __( 'No products selected.', 'skyservers-product-sync-for-emag' ) ] );
        }

        $processed         = 0;
        $skipped_no_custom = 0;

        $rejected_by_cap = 0;

        switch ( $action ) {
            case 'enable_sync':
                $allowance = self::cap_allowance_for( $ids );
                foreach ( $ids as $id ) {
                    if ( PostMeta::is_sync_enabled( $id ) ) {
                        PostMeta::set_sync_enabled( $id, true );
                        $processed++;
                        continue;
                    }
                    if ( $allowance > 0 ) {
                        PostMeta::set_sync_enabled( $id, true );
                        $processed++;
                        $allowance--;
                    } else {
                        $rejected_by_cap++;
                    }
                }
                break;

            case 'disable_sync':
                foreach ( $ids as $id ) { PostMeta::set_sync_enabled( $id, false ); $processed++; }
                break;

            case 'set_margin':
                if ( null === $value || $value < -50 || $value > 300 ) {
                    wp_send_json_error( [ 'message' => __( 'Invalid margin value.', 'skyservers-product-sync-for-emag' ) ] );
                }
                foreach ( $ids as $id ) { PostMeta::set_margin( $id, $value ); $processed++; }
                break;

            case 'clear_margin':
                foreach ( $ids as $id ) { PostMeta::set_margin( $id, null ); $processed++; }
                break;

            case 'increase_margin':
            case 'decrease_margin':
                if ( null === $value || $value < 0 || $value > 300 ) {
                    wp_send_json_error( [ 'message' => __( 'Enter a positive delta between 0 and 300.', 'skyservers-product-sync-for-emag' ) ] );
                }
                $delta = 'increase_margin' === $action ? $value : -$value;
                foreach ( $ids as $id ) {
                    $current = PostMeta::get_margin( $id );
                    if ( null === $current ) {
                        // Grandfathered: products that inherit the global margin are left
                        // untouched (matches the "don't silently change defaults" rule).
                        $skipped_no_custom++;
                        continue;
                    }
                    $new = max( -50.0, min( 300.0, (float) $current + $delta ) );
                    PostMeta::set_margin( $id, $new );
                    $processed++;
                }
                break;

            case 'set_stock_pct':
                if ( null === $value || $value < 0 || $value > 100 ) {
                    wp_send_json_error( [ 'message' => __( 'Stock % must be 0–100.', 'skyservers-product-sync-for-emag' ) ] );
                }
                foreach ( $ids as $id ) { PostMeta::set_stock_percent( $id, $value ); $processed++; }
                break;

            case 'clear_stock_pct':
                foreach ( $ids as $id ) { PostMeta::set_stock_percent( $id, null ); $processed++; }
                break;

            case 'sync_selected':
                // 3.8.0: dispatch to Action Scheduler in 25-product chunks
                // and return immediately. JS detects the sync_id field in
                // the response and opens the modal progress UI; polling
                // takes over from there. The legacy result-counters branch
                // is preserved for the AS-missing fallback path.
                $dispatch = \SkyEmagSync\Sync\Dispatcher::enqueue_for_ids( $ids, 'bulk_products' );

                if ( ! empty( $dispatch['already_running'] ) ) {
                    wp_send_json_success( [
                        'already_running' => true,
                        'sync_id'         => $dispatch['sync_id'],
                        'message'         => __( 'A sync is already running — opening its progress.', 'skyservers-product-sync-for-emag' ),
                    ] );
                }

                if ( ! empty( $dispatch['fallback_sync'] ) ) {
                    $r = $dispatch['fallback_result'];
                    wp_send_json_success( [
                        'fallback' => true,
                        'message'  => sprintf(
                            /* translators: %1$d: number, %2$d: number, %3$d: number */
                            __( 'Sync: %1$d synced, %2$d skipped, %3$d failed.', 'skyservers-product-sync-for-emag' ),
                            $r['synced'], $r['skipped'], $r['failed']
                        ),
                        'processed' => $r['synced'],
                    ] );
                }

                if ( ! $dispatch['success'] ) {
                    wp_send_json_error( [
                        'message' => $dispatch['errors'][0] ?? __( 'Sync could not be started.', 'skyservers-product-sync-for-emag' ),
                    ] );
                }

                wp_send_json_success( [
                    'sync_id'        => $dispatch['sync_id'],
                    'total_products' => (int) $dispatch['total_products'],
                    'total_batches'  => (int) $dispatch['total_batches'],
                    'enqueued'       => true,
                ] );
                return;

            case 'deactivate_emag':
            case 'activate_emag':
                $new_status = 'deactivate_emag' === $action ? 0 : 1;
                $client     = new \SkyEmagSync\Api\EmagClient();
                $batch      = [];
                foreach ( $ids as $id ) {
                    $veid = PostMeta::get_vendor_ext_id( $id );
                    if ( null === $veid ) { continue; }
                    update_post_meta( $id, PostMeta::EMAG_STATUS, $new_status );
                    $batch[] = $new_status === 0
                        ? OfferPayload::inactive( $veid )
                        : [ 'id' => $veid, 'status' => 1 ];
                    $processed++;
                }
                if ( ! empty( $batch ) ) {
                    $client->save_offers( $batch );
                }
                $label = $new_status === 0
                    /* translators: %1$d: number */
                    ? __( '%d product(s) deactivated on eMAG.', 'skyservers-product-sync-for-emag' )
                    /* translators: %1$d: number */
                    : __( '%d product(s) activated on eMAG.', 'skyservers-product-sync-for-emag' );
                wp_send_json_success( [
                    'message'   => sprintf( $label, $processed ),
                    'processed' => $processed,
                ] );
                return;

            default:
                wp_send_json_error( [ 'message' => __( 'Unknown action.', 'skyservers-product-sync-for-emag' ) ] );
        }

        $message = sprintf(
            /* translators: %d: number of products updated */
            __( '%d product(s) updated.', 'skyservers-product-sync-for-emag' ),
            $processed
        );
        if ( $skipped_no_custom > 0 ) {
            $message .= ' ' . sprintf(
                /* translators: %d: count of products without a custom margin set */
                __( '%d skipped (no custom margin; global used).', 'skyservers-product-sync-for-emag' ),
                $skipped_no_custom
            );
        }

        wp_send_json_success( [
            'message'           => $message,
            'processed'         => $processed,
            'skipped_no_custom' => $skipped_no_custom,
        ] );
    }

    // ── POST fallback ─────────────────────────────────────────────────────

    public function handle_bulk_post(): void {
        if ( ! isset( $_POST['skyemag_bulk_nonce'] ) ) {
            return;
        }
        check_admin_referer( 'skyemag_bulk_action', 'skyemag_bulk_nonce' );
        if ( ! current_user_can( Menu::CAPABILITY ) ) {
            return;
        }

        $action    = sanitize_key( $_POST['action'] ?? '' );
        $ids       = array_map( 'intval', (array) ( $_POST['product_ids'] ?? [] ) );
        $processed = 0;

        switch ( $action ) {
            case 'enable_sync':
                foreach ( $ids as $id ) { PostMeta::set_sync_enabled( $id, true );  $processed++; }
                break;
            case 'disable_sync':
                foreach ( $ids as $id ) { PostMeta::set_sync_enabled( $id, false ); $processed++; }
                break;
            case 'clear_margin':
                foreach ( $ids as $id ) { PostMeta::set_margin( $id, null );        $processed++; }
                break;
            case 'clear_stock_pct':
                foreach ( $ids as $id ) { PostMeta::set_stock_percent( $id, null ); $processed++; }
                break;
        }

        wp_safe_redirect( add_query_arg( [
            'page'            => Menu::PRODUCTS_SLUG,
            'skyemag_updated' => $processed,
        ], admin_url( 'admin.php' ) ) );
        exit;
    }

    // ── Duplicate eMAG mappings (unlink + repair) ─────────────────────────

    /**
     * Find eMAG offer ids claimed by more than one product.
     *
     * @return array<int, int[]>  [ vendor_ext_id => [post_id, ...] ]
     */
    public static function find_duplicate_mappings(): array {
        global $wpdb;

        $rows = $wpdb->get_results( $wpdb->prepare(
            "SELECT pm.meta_value AS vendor_id, GROUP_CONCAT(pm.post_id) AS post_ids
             FROM {$wpdb->postmeta} pm
             INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
             WHERE pm.meta_key = %s
               AND pm.meta_value != ''
               AND p.post_status NOT IN ('trash','auto-draft')
             GROUP BY pm.meta_value
             HAVING COUNT(*) > 1",
            PostMeta::VENDOR_EXT_ID
        ), ARRAY_A );

        $groups = [];
        foreach ( (array) $rows as $row ) {
            $ids = array_map( 'intval', explode( ',', (string) $row['post_ids'] ) );
            if ( count( $ids ) > 1 ) {
                $groups[ (int) $row['vendor_id'] ] = $ids;
            }
        }
        return $groups;
    }

    /**
     * Warning box on the Products page when duplicate mappings exist,
     * with a one-click repair button for the unambiguous groups.
     */
    private static function render_duplicate_mappings_notice(): void {
        $groups = self::find_duplicate_mappings();
        if ( empty( $groups ) ) {
            return;
        }

        $fixable = 0;
        ?>
        <div class="notice notice-warning" style="padding:12px 14px;">
            <p style="margin-top:0;"><strong>
                <?php esc_html_e( 'Duplicate eMAG mappings detected', 'skyservers-product-sync-for-emag' ); ?>
            </strong></p>
            <p><?php esc_html_e( 'The products below share the same eMAG offer id — usually the result of duplicating an already-mapped product in WooCommerce. Their sync is blocked to protect the eMAG offers. The product whose SKU matches the eMAG part_number keeps the mapping; the others should be unlinked.', 'skyservers-product-sync-for-emag' ); ?></p>
            <ul style="list-style:disc;margin-left:20px;">
                <?php foreach ( $groups as $vendor_id => $post_ids ) :
                    $keeper = Runner::resolve_mapping_keeper( $post_ids );
                    if ( null !== $keeper ) {
                        $fixable++;
                    }
                    ?>
                    <li>
                        <strong>eMAG #<?php echo (int) $vendor_id; ?></strong> —
                        <?php foreach ( $post_ids as $i => $pid ) :
                            $title = get_the_title( $pid );
                            $label = $title ? $title : "#{$pid}";
                            ?>
                            <?php echo $i > 0 ? ' · ' : ''; ?>
                            <a href="<?php echo esc_url( get_edit_post_link( $pid ) ); ?>">
                                <?php echo esc_html( wp_html_excerpt( $label, 60, '…' ) ); ?>
                            </a>
                            <?php if ( $pid === $keeper ) : ?>
                                <span class="skyemag-badge skyemag-badge--success"><?php esc_html_e( 'keeps mapping', 'skyservers-product-sync-for-emag' ); ?></span>
                            <?php else : ?>
                                <span class="skyemag-badge skyemag-badge--error"><?php esc_html_e( 'will be unlinked', 'skyservers-product-sync-for-emag' ); ?></span>
                            <?php endif; ?>
                        <?php endforeach; ?>
                        <?php if ( null === $keeper ) : ?>
                            <em><?php esc_html_e( '(ambiguous — no SKU matches the eMAG part_number; resolve manually with per-product Unlink)', 'skyservers-product-sync-for-emag' ); ?></em>
                        <?php endif; ?>
                    </li>
                <?php endforeach; ?>
            </ul>
            <?php if ( $fixable > 0 ) : ?>
                <p>
                    <button type="button" class="button button-primary" id="skyemag-fix-duplicates">
                        <?php esc_html_e( 'Fix automatically', 'skyservers-product-sync-for-emag' ); ?>
                    </button>
                    <span id="skyemag-fix-duplicates-status"></span>
                </p>
            <?php endif; ?>
        </div>
        <?php
    }

    /**
     * AJAX: remove the eMAG mapping from one product.
     */
    public function ajax_unlink_product(): void {
        $this->verify_ajax_nonce();
        // phpcs:disable WordPress.Security.NonceVerification.Missing -- nonce verified by verify_ajax_nonce() above.
        $product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
        // phpcs:enable WordPress.Security.NonceVerification.Missing

        if ( ! $product_id || ! wc_get_product( $product_id ) ) {
            wp_send_json_error( [ 'message' => __( 'Invalid product.', 'skyservers-product-sync-for-emag' ) ] );
        }

        $vendor_id = PostMeta::get_vendor_ext_id( $product_id );
        if ( null === $vendor_id ) {
            wp_send_json_error( [ 'message' => __( 'This product is not mapped to an eMAG offer.', 'skyservers-product-sync-for-emag' ) ] );
        }

        PostMeta::clear_mapping( $product_id );
        \SkyEmagSync\Sync\Logger::log( $product_id, 0, 'unlink', \SkyEmagSync\Sync\Logger::RESULT_SUCCESS,
            "Unlinked from eMAG offer #{$vendor_id} (manual action)" );

        wp_send_json_success( [
            'product_id' => $product_id,
            'message'    => sprintf(
                /* translators: %d: eMAG offer id */
                __( 'Product unlinked from eMAG offer #%d.', 'skyservers-product-sync-for-emag' ),
                $vendor_id
            ),
        ] );
    }

    /**
     * AJAX: repair all duplicate mappings that have an unambiguous keeper.
     */
    public function ajax_fix_duplicate_mappings(): void {
        $this->verify_ajax_nonce();

        $groups    = self::find_duplicate_mappings();
        $unlinked  = 0;
        $ambiguous = 0;

        foreach ( $groups as $vendor_id => $post_ids ) {
            $keeper = Runner::resolve_mapping_keeper( $post_ids );
            if ( null === $keeper ) {
                $ambiguous++;
                continue;
            }
            foreach ( $post_ids as $pid ) {
                if ( $pid === $keeper ) {
                    continue;
                }
                PostMeta::clear_mapping( $pid );
                \SkyEmagSync\Sync\Logger::log( $pid, 0, 'unlink', \SkyEmagSync\Sync\Logger::RESULT_SUCCESS,
                    "Unlinked from eMAG offer #{$vendor_id} (duplicate-mapping repair; kept product #{$keeper})" );
                $unlinked++;
            }
        }

        $message = sprintf(
            /* translators: %d: number of products */
            __( 'Repair complete: %d product(s) unlinked.', 'skyservers-product-sync-for-emag' ),
            $unlinked
        );
        if ( $ambiguous > 0 ) {
            $message .= ' ' . sprintf(
                /* translators: %d: number of groups */
                __( '%d group(s) are ambiguous and need manual per-product Unlink.', 'skyservers-product-sync-for-emag' ),
                $ambiguous
            );
        }

        wp_send_json_success( [
            'unlinked'  => $unlinked,
            'ambiguous' => $ambiguous,
            'message'   => $message,
        ] );
    }

    // ── Private helpers ───────────────────────────────────────────────────

    private function verify_ajax_nonce(): void {
        if ( ! check_ajax_referer( self::NONCE_ACTION, 'nonce', false ) ) {
            wp_send_json_error( [ 'message' => 'Security check failed.' ], 403 );
        }
        if ( ! current_user_can( Menu::CAPABILITY ) ) {
            wp_send_json_error( [ 'message' => 'Unauthorized.' ], 403 );
        }
    }

    private function get_product_or_fail( int $product_id ): \WC_Product {
        if ( ! $product_id ) {
            wp_send_json_error( [ 'message' => __( 'Invalid product ID.', 'skyservers-product-sync-for-emag' ) ] );
        }
        $product = wc_get_product( $product_id );
        if ( ! $product ) {
            wp_send_json_error( [ 'message' => __( 'Product not found.', 'skyservers-product-sync-for-emag' ) ] );
        }
        return $product;
    }

    private static function format_price( ?float $price ): ?string {
        return $price !== null ? number_format( $price, 2 ) : null;
    }

    private static function render_notices(): void {
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only display of a flash notice value, no state mutation.
        if ( ! empty( $_GET['skyemag_updated'] ) ) {
            $count = absint( wp_unslash( $_GET['skyemag_updated'] ) );
            printf(
                '<div class="notice notice-success is-dismissible"><p>%s</p></div>',
                /* translators: %d: number of products updated */
                esc_html( sprintf( __( '%d product(s) updated.', 'skyservers-product-sync-for-emag' ), $count ) )
            );
        }
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
    }
}
