<?php
/**
 * WP_List_Table for SkyServers Sync products.
 *
 * @package SkyEmagSync
 */

namespace SkyEmagSync\Admin;

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

defined( 'ABSPATH' ) || exit;

if ( ! class_exists( 'WP_List_Table' ) ) {
    require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

class ProductsTable extends \WP_List_Table {

    private int $per_page = 25;

    public function __construct() {
        parent::__construct( [
            'singular' => 'product',
            'plural'   => 'products',
            'ajax'     => false,
        ] );
    }

    protected function get_table_classes(): array {
        $classes   = parent::get_table_classes();
        $classes[] = 'skyemag-products-table';
        return $classes;
    }

    public function get_columns(): array {
        return [
            'cb'            => '<input type="checkbox" />',
            'sync_enabled'  => __( 'Sync', 'skyservers-product-sync-for-emag' ),
            'name'          => __( 'Product / Variation', 'skyservers-product-sync-for-emag' ),
            'sku'           => __( 'SKU / eMAG ID', 'skyservers-product-sync-for-emag' ),
            'ean'           => __( 'EAN', 'skyservers-product-sync-for-emag' ),
            'type'          => __( 'Type', 'skyservers-product-sync-for-emag' ),
            'stock'         => __( 'WC Stock', 'skyservers-product-sync-for-emag' ),
            'woo_price'     => __( 'Woo Price', 'skyservers-product-sync-for-emag' ),
            'margin'        => __( 'Margin %', 'skyservers-product-sync-for-emag' ),
            'min_margin'    => __( 'Min %', 'skyservers-product-sync-for-emag' ),
            'max_margin'    => __( 'Max %', 'skyservers-product-sync-for-emag' ),
            'stock_percent' => __( 'Stock %', 'skyservers-product-sync-for-emag' ),
            'emag_price'    => __( 'eMAG Price', 'skyservers-product-sync-for-emag' ),
            'emag_stock'    => __( 'eMAG Stock', 'skyservers-product-sync-for-emag' ),
            'buy_button'    => __( 'Buy Button', 'skyservers-product-sync-for-emag' ),
            'last_sync'     => __( 'Last Sync', 'skyservers-product-sync-for-emag' ),
            'status'        => __( 'Status', 'skyservers-product-sync-for-emag' ),
        ];
    }

    public function get_sortable_columns(): array {
        return [
            'name'      => [ 'title', false ],
            'sku'       => [ 'sku', false ],
            'woo_price' => [ 'price', false ],
            'stock'     => [ 'stock', false ],
            'last_sync' => [ 'last_sync', false ],
        ];
    }

    protected function get_bulk_actions(): array {
        return [
            'enable_sync'       => __( 'Enable sync', 'skyservers-product-sync-for-emag' ),
            'disable_sync'      => __( 'Disable sync', 'skyservers-product-sync-for-emag' ),
            'set_margin'        => __( 'Set margin to X%', 'skyservers-product-sync-for-emag' ),
            'increase_margin'   => __( 'Increase margin by X%', 'skyservers-product-sync-for-emag' ),
            'decrease_margin'   => __( 'Decrease margin by X%', 'skyservers-product-sync-for-emag' ),
            'clear_margin'      => __( 'Clear custom margin', 'skyservers-product-sync-for-emag' ),
            'set_stock_pct'     => __( 'Set stock % to X%', 'skyservers-product-sync-for-emag' ),
            'clear_stock_pct'   => __( 'Clear custom stock %', 'skyservers-product-sync-for-emag' ),
            'sync_selected'     => __( 'Sync selected now', 'skyservers-product-sync-for-emag' ),
            'deactivate_emag'   => __( 'Deactivate on eMAG', 'skyservers-product-sync-for-emag' ),
            'activate_emag'     => __( 'Activate on eMAG', 'skyservers-product-sync-for-emag' ),
        ];
    }

    // ── Data ─────────────────────────────────────────────────────────────

    public function prepare_items(): void {
        $this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ];

        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only listing; $_REQUEST values feed the query, no state mutation.
        $search  = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : '';
        $filter  = isset( $_REQUEST['sync_filter'] ) ? sanitize_key( wp_unslash( $_REQUEST['sync_filter'] ) ) : 'mapped';
        $orderby = isset( $_REQUEST['orderby'] ) ? sanitize_key( wp_unslash( $_REQUEST['orderby'] ) ) : 'title';
        $order   = ( isset( $_REQUEST['order'] ) && 'desc' === strtolower( sanitize_key( wp_unslash( $_REQUEST['order'] ) ) ) ) ? 'DESC' : 'ASC';
        $paged   = $this->get_pagenum();

        // Per page from query param.
        $pp = isset( $_REQUEST['per_page'] ) ? absint( wp_unslash( $_REQUEST['per_page'] ) ) : 25;
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        if ( in_array( $pp, [ 20, 50, 100 ], true ) ) {
            $this->per_page = $pp;
        }

        $rows  = $this->load_rows( $search, $filter, $orderby, $order );
        $total = count( $rows );

        $this->set_pagination_args( [
            'total_items' => $total,
            'per_page'    => $this->per_page,
            'total_pages' => ceil( $total / $this->per_page ),
        ] );

        $this->items = array_slice( $rows, ( $paged - 1 ) * $this->per_page, $this->per_page );
    }

    private function load_rows( string $search, string $filter, string $orderby, string $order ): array {
        // wc_get_products supports 'title','sku','price' natively;
        // 'stock' must be sorted manually after building rows.
        $wc_orderby = in_array( $orderby, [ 'title', 'sku', 'price' ], true ) ? $orderby : 'title';

        $args = [
            'status'  => [ 'publish', 'draft', 'private' ],
            'type'    => [ 'simple', 'variable' ],
            'limit'   => -1,
            'return'  => 'objects',
            'orderby' => $wc_orderby,
            'order'   => $order,
        ];

        // Search — match title, SKU, and EAN across products + variations.
        // WordPress' default `s` parameter only searches post_title/post_content,
        // so typing an SKU substring (e.g. "100DG_") returns nothing. We resolve
        // matching parent IDs here and pass them via 'include'.
        if ( '' !== $search ) {
            $parent_ids = self::find_matching_parent_ids( $search );
            if ( empty( $parent_ids ) ) {
                return [];
            }
            $args['include'] = $parent_ids;
        }

        // Category filter — slug from the dropdown in extra_tablenav().
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only filter passthrough, no state mutation.
        $cat_slug = isset( $_REQUEST['skyemag_cat'] ) ? sanitize_title( wp_unslash( $_REQUEST['skyemag_cat'] ) ) : '';
        if ( '' !== $cat_slug && '0' !== $cat_slug ) {
            $args['category'] = [ $cat_slug ];
        }

        $products = wc_get_products( $args );
        $rows     = [];

        foreach ( $products as $product ) {
            if ( $product->is_type( 'variable' ) ) {
                foreach ( $product->get_children() as $variation_id ) {
                    $variation = wc_get_product( $variation_id );
                    if ( ! $variation ) {
                        continue;
                    }
                    $row = $this->build_row( $variation, $product );
                    if ( $this->passes_filter( $row, $filter ) ) {
                        $rows[] = $row;
                    }
                }
            } else {
                $row = $this->build_row( $product );
                if ( $this->passes_filter( $row, $filter ) ) {
                    $rows[] = $row;
                }
            }
        }

        // Manual sort for stock (WC doesn't support meta orderby in wc_get_products)
        if ( 'stock' === $orderby ) {
            usort( $rows, function ( $a, $b ) use ( $order ) {
                $a_stock = $a['stock'] ?? -1;
                $b_stock = $b['stock'] ?? -1;
                return 'ASC' === $order
                    ? $a_stock <=> $b_stock
                    : $b_stock <=> $a_stock;
            } );
        }

        return $rows;
    }

    private function build_row( \WC_Product $product, ?\WC_Product_Variable $parent = null ): array {
        $id             = $product->get_id();
        $vendor_ext_id  = PostMeta::get_vendor_ext_id( $id );
        $source_price   = OfferPayload::source_price( $product );
        $emag_price     = OfferPayload::computed_price( $product );
        $emag_stock     = OfferPayload::computed_stock( $product );

        // VAT rate for this product (from bootstrap), used to show inc./ex. VAT prices
        $vat_id   = PostMeta::get_vat_id( $id );
        $vat_rate = ( null !== $vat_id ) ? Options::vat_rate_for_id( $vat_id ) : null;

        // EAN from WooCommerce native GTIN field
        $ean = PostMeta::get_ean( $id );

        // Publish readiness check runs only on builds that physically ship
        // the readiness helper. The Free WordPress.org build strips it via
        // the Freemius @fs_premium_only directive, so class_exists() alone
        // is the structural test — no license check is layered on top.
        $readiness = class_exists( \SkyEmagSync\Products\PublishReadiness::class )
            ? \SkyEmagSync\Products\PublishReadiness::check( $product )
            : [ 'ready' => false, 'reasons' => [] ];

        if ( $product->is_type( 'variation' ) ) {
            $parent_name = $parent ? $parent->get_name() : get_the_title( $product->get_parent_id() );
            $attrs       = wc_get_formatted_variation( $product, true );
            $name        = $parent_name . ( $attrs ? ' — ' . $attrs : '' );
        } else {
            $name = $product->get_name();
        }

        return [
            'id'             => $id,
            'parent_id'      => $product->is_type( 'variation' ) ? $product->get_parent_id() : 0,
            'name'           => $name,
            'sku'            => $product->get_sku(),
            'ean'            => $ean,
            'vendor_ext_id'  => $vendor_ext_id,
            'type'           => $product->is_type( 'variation' ) ? 'variation' : 'simple',
            'stock'          => $product->get_stock_quantity(),
            'woo_price'      => $source_price,
            'margin'           => PostMeta::get_margin( $id ),
            'min_margin'       => PostMeta::get_min_margin( $id ),
            'max_margin'       => PostMeta::get_max_margin( $id ),
            'stock_percent'    => PostMeta::get_stock_percent( $id ),
            'emag_price'     => $emag_price,
            'emag_stock'     => $emag_stock,
            'last_sync'      => PostMeta::get_last_sync( $id ),
            'last_error'     => PostMeta::get_last_error( $id ),
            'sync_enabled'   => PostMeta::is_sync_enabled( $id ),
            'post_status'    => get_post_status( $id ),
            'min_price'        => PostMeta::get_min_price( $id ),
            'max_price'        => PostMeta::get_max_price( $id ),
            'below_min_margin' => OfferPayload::is_below_min_margin( $product ),
            'vat_rate'         => $vat_rate,  // e.g. 0.11 or 0.21, null if not bootstrapped
            'emag_active'      => OfferPayload::effective_emag_status( $product ),
            'bb_rank'          => PostMeta::get_buy_button_rank( $id ),
            'bb_num_offers'    => PostMeta::get_num_offers( $id ),
            'bb_best_price'    => PostMeta::get_best_offer_price( $id ),
            'bb_updated_at'    => PostMeta::get_bb_updated_at( $id ),
            'val_status'       => PostMeta::get_validation_status( $id ),
            'val_description'  => PostMeta::get_validation_description( $id ),
            'val_errors'       => PostMeta::get_validation_errors( $id ),
            'offer_val'        => PostMeta::get_offer_validation( $id ),
            'publish_ready'    => $readiness['ready'],
            'publish_reasons'  => $readiness['reasons'],
        ];
    }

    private function passes_filter( array $row, string $filter ): bool {
        switch ( $filter ) {
            case 'enabled':   return $row['sync_enabled'];
            case 'disabled':  return ! $row['sync_enabled'];
            case 'mapped':    return null !== $row['vendor_ext_id'];
            case 'unmapped':  return null === $row['vendor_ext_id'];
            case 'errors':    return ! empty( $row['last_error'] );
            case 'ready':     return ! empty( $row['publish_ready'] );
            case 'not_ready':
                return null === $row['vendor_ext_id'] && empty( $row['publish_ready'] );
            case 'inactive':
                return null !== $row['vendor_ext_id'] && 0 === ( $row['emag_active'] ?? 1 );
            case 'rejected':
                return null !== $row['vendor_ext_id']
                    && null !== ( $row['val_status'] ?? null )
                    && in_array( $row['val_status'], [ 5, 6, 8, 10 ], true );
            default:          return true;
        }
    }

    // ── Column renderers ──────────────────────────────────────────────────

    protected function column_cb( $item ): string {
        return sprintf( '<input type="checkbox" name="product_ids[]" value="%d" />', (int) $item['id'] );
    }

    protected function column_sync_enabled( $item ): string {
        $checked = $item['sync_enabled'] ? 'checked' : '';
        return sprintf(
            '<label class="skyemag-toggle" title="%s">
                <input type="checkbox" class="skyemag-sync-toggle" data-id="%d" %s />
                <span class="skyemag-toggle-slider"></span>
            </label>',
            esc_attr__( 'Toggle sync', 'skyservers-product-sync-for-emag' ),
            (int) $item['id'],
            $checked
        );
    }

    protected function column_name( $item ): string {
        $url  = get_edit_post_link( $item['id'] );
        $out  = sprintf( '<strong><a href="%s">%s</a></strong>', esc_url( $url ), esc_html( $item['name'] ) );
        if ( 'draft' === $item['post_status'] ) {
            $out .= ' <span class="post-state">(' . esc_html__( 'Draft', 'skyservers-product-sync-for-emag' ) . ')</span>';
        }

        // Row action: Activate / Deactivate on eMAG (only for mapped products).
        // Placed on a separate line under the product name with explicit colour
        // coding (red for Deactivate, green for Activate) to prevent mis-clicks
        // on the product title link directly above.
        if ( null !== $item['vendor_ext_id'] ) {
            $is_active = ( $item['emag_active'] ?? 1 ) === 1;
            $out .= '<div class="skyemag-emag-actions">';
            if ( $is_active ) {
                $out .= sprintf(
                    '<button type="button" class="button-link skyemag-status-toggle-btn skyemag-action-deactivate" data-product-id="%d" data-new-status="0" title="%s">%s</button>',
                    (int) $item['id'],
                    esc_attr__( 'Send status=0 to eMAG — product will be hidden from buyers', 'skyservers-product-sync-for-emag' ),
                    esc_html__( 'Deactivate', 'skyservers-product-sync-for-emag' )
                );
            } else {
                $out .= sprintf(
                    '<button type="button" class="button-link skyemag-status-toggle-btn skyemag-action-activate" data-product-id="%d" data-new-status="1" title="%s">%s</button>',
                    (int) $item['id'],
                    esc_attr__( 'Send status=1 to eMAG — re-activate offer', 'skyservers-product-sync-for-emag' ),
                    esc_html__( 'Activate', 'skyservers-product-sync-for-emag' )
                );
            }
            $out .= '</div>';
        }

        return $out;
    }

    protected function column_sku( $item ): string {
        $sku = $item['sku'] ? '<code>' . esc_html( $item['sku'] ) . '</code>' : '<span class="skyemag-na">—</span>';

        if ( null !== $item['vendor_ext_id'] ) {
            $sku .= sprintf(
                '<br><span class="skyemag-emag-id" title="%s">eMAG #%d</span> <a href="#" class="skyemag-unlink" data-product-id="%d" data-vendor-id="%d" title="%s">%s</a>',
                esc_attr__( 'eMAG vendor_ext_id', 'skyservers-product-sync-for-emag' ),
                (int) $item['vendor_ext_id'],
                (int) $item['id'],
                (int) $item['vendor_ext_id'],
                esc_attr__( 'Remove the eMAG mapping from this product. The eMAG offer itself is not touched.', 'skyservers-product-sync-for-emag' ),
                esc_html__( 'Unlink', 'skyservers-product-sync-for-emag' )
            );
        } else {
            $sku .= '<br><span class="skyemag-badge skyemag-badge--pending" title="' .
                esc_attr__( 'Run Import from eMAG to map this product', 'skyservers-product-sync-for-emag' ) .
                '">' . esc_html__( 'Unmapped', 'skyservers-product-sync-for-emag' ) . '</span>';
        }

        return $sku;
    }

    protected function column_ean( $item ): string {
        $ean = $item['ean'] ?? '';
        if ( '' === $ean ) {
            return '<span class="skyemag-na" title="' .
                esc_attr__( 'Set EAN in WooCommerce product edit (GTIN, UPC, EAN or ISBN field)', 'skyservers-product-sync-for-emag' ) .
                '">—</span>';
        }
        return '<code>' . esc_html( $ean ) . '</code>';
    }

    protected function column_type( $item ): string {
        return esc_html( 'variation' === $item['type']
            ? __( 'Variation', 'skyservers-product-sync-for-emag' )
            : __( 'Simple', 'skyservers-product-sync-for-emag' ) );
    }

    protected function column_stock( $item ): string {
        if ( null === $item['stock'] ) {
            return '<span title="' . esc_attr__( 'Stock not managed', 'skyservers-product-sync-for-emag' ) . '">∞</span>';
        }
        $qty   = (int) $item['stock'];
        $class = $qty <= 0 ? 'skyemag-stock-zero' : '';
        return sprintf( '<span class="%s">%d</span>', esc_attr( $class ), $qty );
    }

    protected function column_woo_price( $item ): string {
        if ( null === $item['woo_price'] ) {
            return '—';
        }

        $ex  = (float) $item['woo_price'];
        $vat = $item['vat_rate'];

        // ex. VAT (main line — what we send to eMAG)
        $out = '<span class="skyemag-woo-price">'
             . '<span class="skyemag-ex-vat">' . number_format( $ex, 2 ) . ' lei</span>'
             . ' <span class="skyemag-vat-label">ex. VAT</span>'
             . '</span>';

        // inc. VAT (informational — what the customer sees)
        if ( null !== $vat && $vat > 0 ) {
            $inc = $ex * ( 1 + $vat );
            $out .= '<br><small class="skyemag-price-inc">'
                  . number_format( $inc, 2 ) . ' lei'
                  . ' inc. VAT (' . round( $vat * 100 ) . '%)</small>';
        }

        // eMAG min/max bounds (ex. VAT — as stored from bootstrap)
        if ( null !== $item['min_price'] ) {
            $min_ex  = (float) $item['min_price'];
            $max_ex  = (float) $item['max_price'];
            if ( null !== $vat && $vat > 0 ) {
                $min_inc = $min_ex * ( 1 + $vat );
                $max_inc = $max_ex * ( 1 + $vat );
                $out .= sprintf(
                    '<br><small class="skyemag-price-bounds" title="eMAG allowed price range">%s – %s ex. VAT<br>%s – %s inc. VAT</small>',
                    esc_html( number_format( $min_ex, 2 ) ),
                    esc_html( number_format( $max_ex, 2 ) ),
                    esc_html( number_format( $min_inc, 2 ) ),
                    esc_html( number_format( $max_inc, 2 ) )
                );
            } else {
                $out .= sprintf(
                    '<br><small class="skyemag-price-bounds">%s – %s</small>',
                    esc_html( number_format( $min_ex, 2 ) ),
                    esc_html( number_format( $max_ex, 2 ) )
                );
            }
        }

        return $out;
    }

    protected function column_margin( $item ): string {
        $id          = (int) $item['id'];
        $value       = $item['margin'] !== null ? $item['margin'] : '';
        $eff         = OfferPayload::effective_margin( wc_get_product( $id ) );
        $placeholder = number_format( $eff, 2 ) . ' (eff.)';
        $history_count = MarginHistory::count_for_product( $id );

        $history_link = '';
        if ( $history_count > 0 ) {
            $history_link = sprintf(
                '<a href="#" class="skyemag-margin-history-link" data-product-id="%d" title="%s">%s</a>',
                $id,
                esc_attr__( 'View margin change history and revert to a previous value', 'skyservers-product-sync-for-emag' ),
                sprintf(
                    /* translators: %d: number of recorded changes */
                    esc_html__( 'history (%d)', 'skyservers-product-sync-for-emag' ),
                    $history_count
                )
            );
        }

        return sprintf(
            '<div class="skyemag-col-input"><input type="number" class="skyemag-margin-input" data-product-id="%d" value="%s" placeholder="%s" step="0.01" min="-50" max="300" /><span class="skyemag-margin-suffix">%%</span><span class="skyemag-margin-saving" style="display:none">…</span></div>%s',
            $id,
            esc_attr( $value ),
            esc_attr( $placeholder ),
            $history_link ? '<div class="skyemag-margin-history-wrap">' . $history_link . '</div>' : ''
        );
    }

    protected function column_min_margin( $item ): string {
        $product     = wc_get_product( $item['id'] );
        $value       = $item['min_margin'] !== null ? $item['min_margin'] : '';
        $eff_margin  = OfferPayload::effective_margin( $product );
        $eff_min     = OfferPayload::effective_min_margin( $product );
        $placeholder = number_format( $eff_min, 2 ) . ' (eff.)';

        $warn = '';
        if ( $item['below_min_margin'] ) {
            $warn = ' <span class="skyemag-min-margin-warn" title="' .
                esc_attr__( 'Calculated price is below minimum margin — sync blocked', 'skyservers-product-sync-for-emag' ) .
                '">⚠</span>';
        }

        return sprintf(
            '<div class="skyemag-col-input"><input type="number" class="skyemag-min-margin-input" data-product-id="%d" data-margin-input=".skyemag-margin-input[data-product-id=\'%d\']" value="%s" placeholder="%s" step="0.01" min="-50" max="300" /><span class="skyemag-margin-suffix">%%</span><span class="skyemag-min-margin-saving" style="display:none">…</span>%s</div>',
            (int) $item['id'],
            (int) $item['id'],
            esc_attr( $value ),
            esc_attr( $placeholder ),
            $warn
        );
    }

    protected function column_max_margin( $item ): string {
        $product     = wc_get_product( $item['id'] );
        $value       = $item['max_margin'] !== null ? $item['max_margin'] : '';
        $eff_max     = OfferPayload::effective_max_margin( $product );
        $placeholder = number_format( $eff_max, 2 ) . ' (eff.)';

        return sprintf(
            '<div class="skyemag-col-input"><input type="number" class="skyemag-max-margin-input" data-product-id="%d" value="%s" placeholder="%s" step="0.01" min="-50" max="1000" /><span class="skyemag-margin-suffix">%%</span><span class="skyemag-max-margin-saving" style="display:none">…</span></div>',
            (int) $item['id'],
            esc_attr( $value ),
            esc_attr( $placeholder )
        );
    }

    protected function column_stock_percent( $item ): string {
        $value       = $item['stock_percent'] !== null ? $item['stock_percent'] : '';
        $eff         = OfferPayload::effective_stock_percent( wc_get_product( $item['id'] ) );
        $placeholder = number_format( $eff, 0 ) . ' (eff.)';

        return sprintf(
            '<div class="skyemag-col-input"><input type="number" class="skyemag-stock-pct-input" data-product-id="%d" value="%s" placeholder="%s" step="1" min="0" max="100" /><span class="skyemag-margin-suffix">%%</span><span class="skyemag-stock-pct-saving" style="display:none">…</span></div>',
            (int) $item['id'],
            esc_attr( $value ),
            esc_attr( $placeholder )
        );
    }

    protected function column_emag_price( $item ): string {
        if ( null === $item['emag_price'] ) {
            return '<span class="skyemag-price-na">—</span>';
        }

        $ex    = (float) $item['emag_price'];
        $vat   = $item['vat_rate'];
        $class = 'skyemag-price-preview';
        $title = '';

        // Warn if outside eMAG bounds
        if ( null !== $item['min_price'] && $ex < $item['min_price'] ) {
            $class .= ' skyemag-price-warn';
            $title  = __( 'Below eMAG minimum price – will be blocked at sync', 'skyservers-product-sync-for-emag' );
        } elseif ( null !== $item['max_price'] && $item['max_price'] > 0 && $ex > $item['max_price'] ) {
            $class .= ' skyemag-price-warn';
            $title  = __( 'Above eMAG maximum price – will be blocked at sync', 'skyservers-product-sync-for-emag' );
        }

        // ex. VAT (main — what is sent to eMAG)
        $out = sprintf(
            '<span class="%s" data-id="%d" data-vat-rate="%s" title="%s">%s</span>'
            . ' <span class="skyemag-vat-label">ex. VAT</span>',
            esc_attr( $class ),
            (int) $item['id'],
            esc_attr( null !== $vat ? $vat : '' ),
            esc_attr( $title ),
            esc_html( number_format( $ex, 2 ) )
        );

        // inc. VAT (what customer sees on eMAG)
        if ( null !== $vat && $vat > 0 ) {
            $inc = $ex * ( 1 + $vat );
            $out .= '<br><small class="skyemag-price-inc">'
                  . esc_html( number_format( $inc, 2 ) )
                  . ' inc. VAT</small>';
        }

        return $out;
    }

    protected function column_emag_stock( $item ): string {
        if ( null === $item['emag_stock'] ) {
            return '<span class="skyemag-stock-zero" title="' .
                esc_attr__( 'WC stock is 0 – will be skipped at sync', 'skyservers-product-sync-for-emag' ) .
                '">0 (skip)</span>';
        }
        return sprintf(
            '<span class="skyemag-emag-stock" data-id="%d">%d</span>',
            (int) $item['id'],
            (int) $item['emag_stock']
        );
    }

    protected function column_buy_button( $item ): string {
        // Only show for mapped products.
        if ( null === $item['vendor_ext_id'] ) {
            return '<span class="skyemag-na">—</span>';
        }

        $rank       = $item['bb_rank'];
        $num_offers = $item['bb_num_offers'];
        $best_price = $item['bb_best_price'];
        $updated    = $item['bb_updated_at'];

        if ( null === $rank ) {
            return '<span class="skyemag-na" title="' .
                esc_attr__( 'Run Bootstrap or wait for Buy Button tracking cron', 'skyservers-product-sync-for-emag' ) .
                '">—</span>';
        }

        // Medal badges for rank.
        $medals = [ 1 => '🥇', 2 => '🥈', 3 => '🥉', 4 => '4' ];
        $medal  = $medals[ $rank ] ?? (string) $rank;

        $class = 'skyemag-bb-rank';
        if ( 1 === $rank ) {
            $class .= ' skyemag-bb-winner';
        } elseif ( $rank >= 3 ) {
            $class .= ' skyemag-bb-low';
        }

        $tooltip_parts = [];
        /* translators: %1$d: number, %2$d: number */
        $tooltip_parts[] = sprintf( __( 'Rank %1$d of %2$d offers', 'skyservers-product-sync-for-emag' ), $rank, $num_offers ?? 0 );
        if ( null !== $best_price ) {
            /* translators: %1$s: string */
            $tooltip_parts[] = sprintf( __( 'Best price: %s ex. VAT', 'skyservers-product-sync-for-emag' ), number_format( $best_price, 2 ) );
        }
        if ( $updated ) {
            $ts = strtotime( $updated );
            /* translators: %1$s: string */
            $tooltip_parts[] = sprintf( __( 'Updated %s ago', 'skyservers-product-sync-for-emag' ), human_time_diff( $ts, time() ) );
        }

        $out = sprintf(
            '<span class="%s" title="%s">%s</span>',
            esc_attr( $class ),
            esc_attr( implode( ' | ', $tooltip_parts ) ),
            $medal
        );

        // Compact info line.
        if ( null !== $num_offers && $num_offers > 1 ) {
            $out .= '<br><small class="skyemag-bb-info">' .
                /* translators: %1$d: number */
                sprintf( esc_html__( '%d offers', 'skyservers-product-sync-for-emag' ), $num_offers ) .
                '</small>';
        }

        return $out;
    }

    protected function column_last_sync( $item ): string {
        if ( empty( $item['last_sync'] ) ) {
            return '<span class="skyemag-never">' . esc_html__( 'Never', 'skyservers-product-sync-for-emag' ) . '</span>';
        }
        // Stored as GMT — append " UTC" so strtotime treats the bare mysql
        // datetime as UTC (WordPress sets PHP's default tz to UTC, so a bare
        // local-tz string would be misinterpreted as UTC and produce an
        // offset-shaped delta against time()).
        $ts      = strtotime( $item['last_sync'] . ' UTC' );
        $display = $ts ? wp_date( 'Y-m-d H:i:s', $ts ) : $item['last_sync'];
        if ( ! $ts ) {
            return '<span title="' . esc_attr( $display ) . '">' . esc_html( $item['last_sync'] ) . '</span>';
        }
        return '<span title="' . esc_attr( $display ) . '">' .
            esc_html( human_time_diff( $ts, time() ) . ' ago' ) . '</span>';
    }

    protected function column_status( $item ): string {
        $out = '';

        // ── Inactive on eMAG ─────────────────────────────────────────────
        if ( null !== $item['vendor_ext_id'] && 0 === ( $item['emag_active'] ?? 1 ) ) {
            $out .= '<span class="skyemag-badge skyemag-badge--inactive" title="' .
                esc_attr__( 'Product is deactivated on eMAG (status=0)', 'skyservers-product-sync-for-emag' ) .
                '">' . esc_html__( 'Inactive', 'skyservers-product-sync-for-emag' ) . '</span> ';
        }

        // ── Sync status (existing products on eMAG) ──────────────────────
        if ( ! empty( $item['last_error'] ) ) {
            $out .= sprintf(
                '<span class="skyemag-badge skyemag-badge--error" title="%s">%s</span>',
                esc_attr( $item['last_error'] ),
                esc_html__( 'Error', 'skyservers-product-sync-for-emag' )
            );
        } elseif ( ! empty( $item['last_sync'] ) ) {
            $out .= '<span class="skyemag-badge skyemag-badge--success">' . esc_html__( 'OK', 'skyservers-product-sync-for-emag' ) . '</span>';
        } elseif ( null !== $item['vendor_ext_id'] ) {
            $out .= '<span class="skyemag-badge skyemag-badge--pending">' . esc_html__( 'Pending', 'skyservers-product-sync-for-emag' ) . '</span>';
        }

        // ── Validation status (mapped products) ─────────────────────────
        $val_status = $item['val_status'] ?? null;
        if ( null !== $item['vendor_ext_id'] && null !== $val_status ) {
            $val_label = PostMeta::validation_label( $val_status );
            $saleable  = PostMeta::is_validation_saleable( $val_status );

            // Build tooltip with errors if any.
            $val_tooltip = $val_label;
            $val_errors  = $item['val_errors'] ?? [];
            if ( ! empty( $val_errors ) ) {
                $err_texts = [];
                foreach ( $val_errors as $e ) {
                    $err_texts[] = is_string( $e ) ? $e : ( $e['message'] ?? wp_json_encode( $e ) );
                }
                $val_tooltip .= ': ' . implode( '; ', $err_texts );
            }

            // Offer validation (price check).
            $offer_val = $item['offer_val'] ?? null;
            if ( 2 === $offer_val ) {
                $val_tooltip .= ' | ' . __( 'Invalid price', 'skyservers-product-sync-for-emag' );
            }

            if ( 9 === $val_status ) {
                $badge_class = 'skyemag-badge--val-approved';
            } elseif ( $saleable ) {
                $badge_class = 'skyemag-badge--val-ok';
            } elseif ( in_array( $val_status, [ 5, 6, 8, 10 ], true ) ) {
                $badge_class = 'skyemag-badge--val-rejected';
            } else {
                $badge_class = 'skyemag-badge--val-pending';
            }

            $out .= sprintf(
                ' <span class="skyemag-badge %s" title="%s">%s</span>',
                esc_attr( $badge_class ),
                esc_attr( $val_tooltip ),
                esc_html( $val_label )
            );
        }

        // ── Publish readiness (products NOT yet on eMAG) ─────────────────
        // The reasons array is populated upstream by PublishReadiness::check()
        // when that helper is present (Premium build only). An empty array
        // means either the product is fully ready or the readiness helper
        // is not part of this build — either way there is nothing to show.
        if ( null === $item['vendor_ext_id'] && ! empty( $item['publish_reasons'] ) ) {
            $reasons = $item['publish_reasons'];
            // Filter out 'already_on_emag' — that's handled above.
            $reasons = array_filter( $reasons, function ( $r ) {
                return 'already_on_emag' !== $r;
            } );

            if ( $item['publish_ready'] ?? false ) {
                $out .= '<span class="skyemag-badge skyemag-badge--ready" title="' .
                    esc_attr__( 'Ready to publish on eMAG', 'skyservers-product-sync-for-emag' ) .
                    '">' . esc_html__( 'Ready', 'skyservers-product-sync-for-emag' ) . '</span>';
            } else {
                $labels = class_exists( \SkyEmagSync\Products\PublishReadiness::class )
                    ? array_map( [ \SkyEmagSync\Products\PublishReadiness::class, 'reason_label' ], $reasons )
                    : $reasons;
                $tooltip = implode( ', ', $labels );
                $out .= '<span class="skyemag-badge skyemag-badge--not-ready" title="' .
                    esc_attr( $tooltip ) .
                    '">' . esc_html__( 'Not Ready', 'skyservers-product-sync-for-emag' ) . '</span>';
            }
        }

        return $out;
    }

    protected function column_default( $item, $column_name ): string {
        return isset( $item[ $column_name ] ) ? esc_html( (string) $item[ $column_name ] ) : '—';
    }

    // ── Filter tabs ───────────────────────────────────────────────────────

    protected function get_views(): array {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only filter tab state, no mutation.
        $current = isset( $_REQUEST['sync_filter'] ) ? sanitize_key( wp_unslash( $_REQUEST['sync_filter'] ) ) : 'all';
        $base    = add_query_arg( [ 'page' => Menu::PRODUCTS_SLUG ], admin_url( 'admin.php' ) );

        $out = [];
        foreach ( [
            'all'      => __( 'All', 'skyservers-product-sync-for-emag' ),
            'enabled'  => __( 'Sync enabled', 'skyservers-product-sync-for-emag' ),
            'disabled' => __( 'Sync disabled', 'skyservers-product-sync-for-emag' ),
            'mapped'    => __( 'Mapped', 'skyservers-product-sync-for-emag' ),
            'unmapped'  => __( 'Unmapped', 'skyservers-product-sync-for-emag' ),
            'ready'     => __( 'Ready to Publish', 'skyservers-product-sync-for-emag' ),
            'not_ready' => __( 'Not Ready', 'skyservers-product-sync-for-emag' ),
            'inactive'  => __( 'Inactive on eMAG', 'skyservers-product-sync-for-emag' ),
            'rejected'  => __( 'Rejected', 'skyservers-product-sync-for-emag' ),
            'errors'    => __( 'Has errors', 'skyservers-product-sync-for-emag' ),
        ] as $key => $label ) {
            $url   = 'all' === $key
                ? remove_query_arg( 'sync_filter', $base )
                : add_query_arg( 'sync_filter', $key, $base );
            $class = $key === $current ? 'current' : '';
            $out[ $key ] = sprintf( '<a href="%s" class="%s">%s</a>', esc_url( $url ), $class, esc_html( $label ) );
        }

        return $out;
    }

    protected function extra_tablenav( $which ): void {
        if ( 'top' !== $which ) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only filter passthrough, no mutation.
        $current_cat = isset( $_REQUEST['skyemag_cat'] ) ? sanitize_title( wp_unslash( $_REQUEST['skyemag_cat'] ) ) : '';
        $total       = (int) ( $this->get_pagination_arg( 'total_items' ) ?: 0 );
        ?>
        <div class="alignleft actions skyemag-cat-filter-wrap">
            <label for="skyemag-cat-filter" class="screen-reader-text">
                <?php esc_html_e( 'Filter by category', 'skyservers-product-sync-for-emag' ); ?>
            </label>
            <?php
            wp_dropdown_categories( [
                'taxonomy'        => 'product_cat',
                'hide_empty'      => false,
                'show_option_all' => __( 'All categories', 'skyservers-product-sync-for-emag' ),
                'name'            => 'skyemag_cat',
                'id'              => 'skyemag-cat-filter',
                'selected'        => $current_cat,
                'value_field'     => 'slug',
                'hierarchical'    => true,
                'orderby'         => 'name',
            ] );
            submit_button(
                __( 'Filter', 'skyservers-product-sync-for-emag' ),
                '',
                'skyemag_apply_cat_filter',
                false
            );
            ?>
        </div>
        <div class="alignleft actions skyemag-all-filtered-wrap">
            <label title="<?php esc_attr_e( 'Applies the bulk action to every product matching the current filters — not just the ones checked on this page.', 'skyservers-product-sync-for-emag' ); ?>">
                <input type="checkbox" id="skyemag-all-filtered" data-total="<?php echo (int) $total; ?>" />
                <?php
                printf(
                    /* translators: %d: total filtered products across all pages */
                    esc_html__( 'Apply to all %d filtered products', 'skyservers-product-sync-for-emag' ),
                    (int) $total
                );
                ?>
            </label>
        </div>
        <div class="alignleft actions skyemag-bulk-margin-wrap" style="display:none">
            <input type="number" id="skyemag-bulk-value"
                placeholder="%" step="0.01" min="-50" max="300" style="width:80px" />
            <button type="button" class="button" id="skyemag-apply-bulk-value">
                <?php esc_html_e( 'Apply', 'skyservers-product-sync-for-emag' ); ?>
            </button>
        </div>
        <?php
    }

    /**
     * Resolve a search term to a list of parent product IDs to feed into
     * wc_get_products('include' => ...). Matches against title, SKU, and
     * every configured EAN source meta key. Variation matches are promoted
     * to their parent so the whole variable product surfaces.
     *
     * @return int[]
     */
    private static function find_matching_parent_ids( string $search ): array {
        global $wpdb;

        $search = trim( $search );
        if ( '' === $search ) {
            return [];
        }
        $like = '%' . $wpdb->esc_like( $search ) . '%';

        // 1) post_title match across products + variations.
        $title_ids = $wpdb->get_col( $wpdb->prepare(
            "SELECT ID FROM {$wpdb->posts}
             WHERE post_type IN ('product','product_variation')
               AND post_status IN ('publish','draft','private')
               AND post_title LIKE %s",
            $like
        ) );

        // 2) SKU match (_sku meta).
        $sku_ids = $wpdb->get_col( $wpdb->prepare(
            "SELECT DISTINCT post_id FROM {$wpdb->postmeta}
             WHERE meta_key = '_sku' AND meta_value LIKE %s",
            $like
        ) );

        // 3) EAN match across every configured source key.
        $ean_ids = [];
        $ean_keys = \SkyEmagSync\Options::ean_sources();
        if ( ! empty( $ean_keys ) ) {
            $placeholders = implode( ',', array_fill( 0, count( $ean_keys ), '%s' ) );
            $params       = $ean_keys;
            $params[]     = $like;
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $ean_ids = $wpdb->get_col( $wpdb->prepare(
                "SELECT DISTINCT post_id FROM {$wpdb->postmeta}
                 WHERE meta_key IN ({$placeholders}) AND meta_value LIKE %s",
                $params
            ) );
        }

        $all = array_unique( array_map( 'intval', array_merge( (array) $title_ids, (array) $sku_ids, (array) $ean_ids ) ) );
        if ( empty( $all ) ) {
            return [];
        }

        // Promote every variation match to its parent so wc_get_products(include=>...)
        // returns variable products whose children matched.
        $parents = [];
        foreach ( $all as $id ) {
            if ( get_post_type( $id ) === 'product_variation' ) {
                $parent = wp_get_post_parent_id( $id );
                if ( $parent > 0 ) {
                    $parents[] = (int) $parent;
                }
            } else {
                $parents[] = $id;
            }
        }
        return array_values( array_unique( $parents ) );
    }

    /**
     * Cross-page filter: return every product/variation ID that matches the
     * given filter state. Used by the "Apply to all N filtered" bulk action.
     *
     * Supports search, category, and the sync_filter views (enabled, disabled,
     * mapped, unmapped, errors, inactive). Publish-readiness filters fall back
     * to "all matching" because evaluating them requires heavy per-product
     * work — the user can still use per-page checkboxes for those.
     *
     * @return int[]
     */
    public static function query_filtered_ids( string $search, string $sync_filter, string $category ): array {
        $args = [
            'status' => [ 'publish', 'draft', 'private' ],
            'type'   => [ 'simple', 'variable' ],
            'limit'  => -1,
            'return' => 'ids',
        ];
        if ( '' !== $search ) {
            $args['s'] = $search;
        }
        if ( '' !== $category && '0' !== $category ) {
            $args['category'] = [ $category ];
        }

        $parent_ids = (array) wc_get_products( $args );
        $result     = [];

        foreach ( $parent_ids as $pid ) {
            $product = wc_get_product( (int) $pid );
            if ( ! $product ) {
                continue;
            }

            $candidates = $product->is_type( 'variable' )
                ? array_map( 'intval', $product->get_children() )
                : [ (int) $pid ];

            foreach ( $candidates as $cid ) {
                if ( self::id_passes_sync_filter( $cid, $sync_filter ) ) {
                    $result[] = $cid;
                }
            }
        }
        return $result;
    }

    /**
     * Lightweight per-ID check matching the sync_filter view slugs.
     * Fast equivalent of passes_filter() for the cross-page bulk path.
     */
    private static function id_passes_sync_filter( int $id, string $filter ): bool {
        switch ( $filter ) {
            case 'enabled':
                return PostMeta::is_sync_enabled( $id );
            case 'disabled':
                return ! PostMeta::is_sync_enabled( $id );
            case 'mapped':
                return null !== PostMeta::get_vendor_ext_id( $id );
            case 'unmapped':
                return null === PostMeta::get_vendor_ext_id( $id );
            case 'errors':
                return '' !== PostMeta::get_last_error( $id );
            case 'inactive':
                $ext    = PostMeta::get_vendor_ext_id( $id );
                $status = (int) get_post_meta( $id, PostMeta::EMAG_STATUS, true );
                return null !== $ext && 0 === $status;
            default:
                // 'all', 'ready', 'not_ready', 'rejected' — treat as all for cross-page.
                return true;
        }
    }
}
