<?php
/**
 * Export Runner - Processes exports.
 *
 * @package PrimeLink
 */

namespace PrimeLink\Modules\Exports\Service;

use PrimeLink\Modules\Exports\Service\ExportDestinationService;

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

final class ExportRunner {

    /**
     * Get preview data for export.
     *
     * @param string $export_type Export type (products, orders, customers, coupons).
     * @param array  $mapping     Field mapping (from step 2).
     * @param array  $filters     Product filters (from step 3: categories, tags, status).
     * @param array  $row_filters Row filters (from step 2).
     * @param int    $page        Page number.
     * @param int    $per_page    Items per page.
     * @return array Preview data.
     */
    public static function get_preview( $export_type, $mapping = array(), $filters = array(), $row_filters = array(), $page = 1, $per_page = 20 ) {
        switch ( $export_type ) {
            case 'products':
                return self::get_products_preview( $mapping, $filters, $row_filters, $page, $per_page );
            case 'orders':
                return self::get_orders_preview( $mapping, $filters, $row_filters, $page, $per_page );
            case 'customers':
                return self::get_customers_preview( $mapping, $filters, $row_filters, $page, $per_page );
            case 'coupons':
                return self::get_coupons_preview( $mapping, $filters, $row_filters, $page, $per_page );
            case 'categories':
                return self::get_categories_preview( $mapping, $filters, $row_filters, $page, $per_page );
            case 'tags':
                return self::get_tags_preview( $mapping, $filters, $row_filters, $page, $per_page );
            case 'attributes':
                return self::get_attributes_preview( $mapping, $filters, $row_filters, $page, $per_page );
            case 'variations':
                return self::get_variations_preview( $mapping, $filters, $row_filters, $page, $per_page );
            case 'reviews':
                return self::get_reviews_preview( $mapping, $filters, $row_filters, $page, $per_page );
            case 'downloads':
                return self::get_downloads_preview( $mapping, $filters, $row_filters, $page, $per_page );
            default:
                return array(
                    'headers' => array(),
                    'rows'    => array(),
                    'total'   => 0,
                );
        }
    }

    /**
     * Get products preview.
     *
     * @param array $mapping     Field mapping (from step 2).
     * @param array $filters     Product filters (from step 3: categories, tags, status).
     * @param array $row_filters Row filters (from step 2).
     * @param int   $page        Page number.
     * @param int   $per_page    Items per page.
     * @return array
     */
    private static function get_products_preview( $mapping, $filters, $row_filters, $page, $per_page ) {
        $args = array(
            'post_type'      => 'product',
            'posts_per_page' => $per_page,
            'paged'          => $page,
            'post_status'    => 'any',
        );

        // Apply product filters (from step 3)
        if ( ! empty( $filters['categories'] ) ) {
            $args['tax_query'][] = array(
                'taxonomy' => 'product_cat',
                'field'    => 'term_id',
                'terms'    => array_map( 'absint', (array) $filters['categories'] ),
            );
        }

        if ( ! empty( $filters['tags'] ) ) {
            if ( ! isset( $args['tax_query'] ) ) {
                $args['tax_query'] = array();
            }
            $args['tax_query'][] = array(
                'taxonomy' => 'product_tag',
                'field'    => 'term_id',
                'terms'    => array_map( 'absint', (array) $filters['tags'] ),
            );
        }

        if ( ! empty( $filters['status'] ) ) {
            $args['post_status'] = sanitize_text_field( $filters['status'] );
        }

        // Get total count
        $count_args = $args;
        $count_args['posts_per_page'] = -1;
        $count_args['fields'] = 'ids';
        $total = count( get_posts( $count_args ) );

        // Get products
        $products = get_posts( $args );

        // Build headers from mapping
        $headers = array();
        $mapping_order = array();
        
        if ( ! empty( $mapping ) && is_array( $mapping ) ) {
            foreach ( $mapping as $column_key => $column_config ) {
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                // Get header from config or use column key
                $header = '';
                if ( ! empty( $column_config['config'] ) ) {
                    $config = json_decode( $column_config['config'], true );
                    if ( is_array( $config ) && ! empty( $config['column_header'] ) ) {
                        $header = $config['column_header'];
                    }
                }
                
                // If no custom header, use field label
                if ( empty( $header ) ) {
                    $source = $column_config['source'] ?? '';
                    $header = self::get_field_label( $source, 'products' );
                }
                
                if ( ! empty( $header ) ) {
                    $headers[] = $header;
                    $mapping_order[] = $column_key;
                }
            }
        }
        
        // If no mapping, use default fields
        if ( empty( $headers ) ) {
            $default_mapping = array(
                'id'          => array( 'source' => 'id' ),
                'title'       => array( 'source' => 'title' ),
                'sku'         => array( 'source' => 'sku' ),
                'price'       => array( 'source' => 'price' ),
            );
            foreach ( $default_mapping as $key => $config ) {
                $headers[] = self::get_field_label( $config['source'], 'products' );
                $mapping_order[] = $key;
            }
            $mapping = $default_mapping;
        }

        // Build rows
        $rows = array();
        foreach ( $products as $product ) {
            $product_data = self::get_product_data( $product->ID );
            
            // Apply row filters if any
            if ( ! empty( $row_filters ) && ! self::product_matches_filters( $product_data, $row_filters ) ) {
                continue;
            }
            
            $row = array();
            foreach ( $mapping_order as $column_key ) {
                $column_config = $mapping[ $column_key ] ?? array();
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $value = self::get_mapped_value( $product_data, $column_config );
                $row[] = $value;
            }
            $rows[] = $row;
        }

        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
            'page'    => $page,
            'per_page' => $per_page,
        );
    }

    /**
     * Get field label.
     *
     * @param string $field Field name.
     * @param string $export_type Export type (products, orders, customers, coupons).
     * @return string Field label.
     */
    private static function get_field_label( $field, $export_type = 'products' ) {
        $labels = array();
        
        if ( $export_type === 'products' ) {
            $labels = array(
                'id'                => __( 'Product ID', 'primelink' ),
                'title'             => __( 'Product Title', 'primelink' ),
                'sku'               => __( 'SKU', 'primelink' ),
                'price'             => __( 'Regular Price', 'primelink' ),
                'sale_price'        => __( 'Sale Price', 'primelink' ),
                'stock_quantity'    => __( 'Stock Quantity', 'primelink' ),
                'stock_status'      => __( 'Stock Status', 'primelink' ),
                'manage_stock'      => __( 'Manage Stock', 'primelink' ),
                'description'       => __( 'Description', 'primelink' ),
                'short_description' => __( 'Short Description', 'primelink' ),
                'categories'        => __( 'Categories', 'primelink' ),
                'tags'              => __( 'Tags', 'primelink' ),
                'featured_image'    => __( 'Featured Image URL', 'primelink' ),
                'gallery_images'    => __( 'Gallery Image URLs', 'primelink' ),
                'weight'            => __( 'Weight', 'primelink' ),
                'length'            => __( 'Length', 'primelink' ),
                'width'             => __( 'Width', 'primelink' ),
                'height'            => __( 'Height', 'primelink' ),
                'product_type'      => __( 'Product Type', 'primelink' ),
                'status'            => __( 'Status', 'primelink' ),
                'date_created'      => __( 'Date Created', 'primelink' ),
                'date_modified'     => __( 'Date Modified', 'primelink' ),
            );
        } elseif ( $export_type === 'categories' ) {
            $labels = array(
                'category_id'   => __( 'Category ID', 'primelink' ),
                'name'          => __( 'Category Name', 'primelink' ),
                'slug'          => __( 'Slug', 'primelink' ),
                'description'   => __( 'Description', 'primelink' ),
                'parent'        => __( 'Parent ID', 'primelink' ),
                'parent_name'   => __( 'Parent Name', 'primelink' ),
                'parent_slug'   => __( 'Parent Slug', 'primelink' ),
                'thumbnail'     => __( 'Thumbnail URL', 'primelink' ),
                'thumbnail_id'  => __( 'Thumbnail ID', 'primelink' ),
                'display_type'  => __( 'Display Type', 'primelink' ),
                'count'         => __( 'Product Count', 'primelink' ),
            );
        } elseif ( $export_type === 'tags' ) {
            $labels = array(
                'tag_id'        => __( 'Tag ID', 'primelink' ),
                'name'          => __( 'Tag Name', 'primelink' ),
                'slug'          => __( 'Slug', 'primelink' ),
                'description'   => __( 'Description', 'primelink' ),
                'thumbnail'     => __( 'Thumbnail URL', 'primelink' ),
                'thumbnail_id'  => __( 'Thumbnail ID', 'primelink' ),
                'count'         => __( 'Product Count', 'primelink' ),
            );
        } elseif ( $export_type === 'attributes' ) {
            $labels = array(
                'attribute_id'  => __( 'Attribute ID', 'primelink' ),
                'name'          => __( 'Attribute Name', 'primelink' ),
                'slug'          => __( 'Slug', 'primelink' ),
                'taxonomy'      => __( 'Taxonomy Name', 'primelink' ),
                'type'          => __( 'Type', 'primelink' ),
                'order_by'      => __( 'Order By', 'primelink' ),
                'has_archives'  => __( 'Has Archives', 'primelink' ),
                'term_count'    => __( 'Term Count', 'primelink' ),
                'term_names'    => __( 'Term Names (comma-separated)', 'primelink' ),
                'term_slugs'    => __( 'Term Slugs (comma-separated)', 'primelink' ),
                'term_ids'      => __( 'Term IDs (comma-separated)', 'primelink' ),
                'terms'         => __( 'Terms (JSON)', 'primelink' ),
            );
        } elseif ( $export_type === 'variations' ) {
            $labels = array(
                'variation_id'      => __( 'Variation ID', 'primelink' ),
                'parent_id'         => __( 'Parent Product ID', 'primelink' ),
                'parent_sku'        => __( 'Parent Product SKU', 'primelink' ),
                'parent_title'      => __( 'Parent Product Title', 'primelink' ),
                'sku'               => __( 'SKU', 'primelink' ),
                'regular_price'     => __( 'Regular Price', 'primelink' ),
                'sale_price'        => __( 'Sale Price', 'primelink' ),
                'stock_quantity'    => __( 'Stock Quantity', 'primelink' ),
                'stock_status'      => __( 'Stock Status', 'primelink' ),
                'manage_stock'      => __( 'Manage Stock', 'primelink' ),
                'backorders'        => __( 'Backorders', 'primelink' ),
                'weight'            => __( 'Weight', 'primelink' ),
                'length'            => __( 'Length', 'primelink' ),
                'width'             => __( 'Width', 'primelink' ),
                'height'            => __( 'Height', 'primelink' ),
                'image'             => __( 'Image URL', 'primelink' ),
                'image_id'          => __( 'Image ID', 'primelink' ),
                'gallery'           => __( 'Gallery URLs (comma-separated)', 'primelink' ),
                'gallery_ids'       => __( 'Gallery IDs (comma-separated)', 'primelink' ),
                'description'       => __( 'Description', 'primelink' ),
                'short_description' => __( 'Short Description', 'primelink' ),
                'attributes'        => __( 'Attributes (JSON)', 'primelink' ),
                'tax_class'         => __( 'Tax Class', 'primelink' ),
                'status'            => __( 'Status', 'primelink' ),
                'date_created'      => __( 'Date Created', 'primelink' ),
                'date_modified'     => __( 'Date Modified', 'primelink' ),
            );
        } elseif ( $export_type === 'orders' ) {
            $labels = array(
                'order_id'          => __( 'Order ID', 'primelink' ),
                'order_number'      => __( 'Order Number', 'primelink' ),
                'status'            => __( 'Order Status', 'primelink' ),
                'date_created'      => __( 'Date Created', 'primelink' ),
                'date_modified'     => __( 'Date Modified', 'primelink' ),
                'date_paid'         => __( 'Date Paid', 'primelink' ),
                'date_completed'    => __( 'Date Completed', 'primelink' ),
                'customer_id'       => __( 'Customer ID', 'primelink' ),
                'customer_email'    => __( 'Customer Email', 'primelink' ),
                'customer_name'     => __( 'Customer Name', 'primelink' ),
                'billing_first_name' => __( 'Billing First Name', 'primelink' ),
                'billing_last_name' => __( 'Billing Last Name', 'primelink' ),
                'billing_company'   => __( 'Billing Company', 'primelink' ),
                'billing_address_1' => __( 'Billing Address 1', 'primelink' ),
                'billing_address_2' => __( 'Billing Address 2', 'primelink' ),
                'billing_city'     => __( 'Billing City', 'primelink' ),
                'billing_state'    => __( 'Billing State', 'primelink' ),
                'billing_postcode' => __( 'Billing Postcode', 'primelink' ),
                'billing_country'  => __( 'Billing Country', 'primelink' ),
                'billing_email'    => __( 'Billing Email', 'primelink' ),
                'billing_phone'    => __( 'Billing Phone', 'primelink' ),
                'shipping_first_name' => __( 'Shipping First Name', 'primelink' ),
                'shipping_last_name' => __( 'Shipping Last Name', 'primelink' ),
                'shipping_company' => __( 'Shipping Company', 'primelink' ),
                'shipping_address_1' => __( 'Shipping Address 1', 'primelink' ),
                'shipping_address_2' => __( 'Shipping Address 2', 'primelink' ),
                'shipping_city'    => __( 'Shipping City', 'primelink' ),
                'shipping_state'   => __( 'Shipping State', 'primelink' ),
                'shipping_postcode' => __( 'Shipping Postcode', 'primelink' ),
                'shipping_country' => __( 'Shipping Country', 'primelink' ),
                'payment_method'   => __( 'Payment Method', 'primelink' ),
                'payment_method_title' => __( 'Payment Method Title', 'primelink' ),
                'transaction_id'   => __( 'Transaction ID', 'primelink' ),
                'order_total'      => __( 'Order Total', 'primelink' ),
                'order_subtotal'   => __( 'Order Subtotal', 'primelink' ),
                'order_tax'        => __( 'Order Tax', 'primelink' ),
                'order_shipping'   => __( 'Order Shipping', 'primelink' ),
                'order_discount'   => __( 'Order Discount', 'primelink' ),
                'currency'         => __( 'Currency', 'primelink' ),
                'line_items'       => __( 'Line Items', 'primelink' ),
                'customer_note'    => __( 'Customer Note', 'primelink' ),
            );
        } elseif ( $export_type === 'customers' ) {
            $labels = array(
                'customer_id'       => __( 'Customer ID', 'primelink' ),
                'email'             => __( 'Email', 'primelink' ),
                'username'          => __( 'Username', 'primelink' ),
                'first_name'        => __( 'First Name', 'primelink' ),
                'last_name'         => __( 'Last Name', 'primelink' ),
                'display_name'      => __( 'Display Name', 'primelink' ),
                'role'              => __( 'Role', 'primelink' ),
                'date_registered'   => __( 'Date Registered', 'primelink' ),
                'billing_first_name' => __( 'Billing First Name', 'primelink' ),
                'billing_last_name' => __( 'Billing Last Name', 'primelink' ),
                'billing_company'   => __( 'Billing Company', 'primelink' ),
                'billing_address_1' => __( 'Billing Address 1', 'primelink' ),
                'billing_address_2' => __( 'Billing Address 2', 'primelink' ),
                'billing_city'      => __( 'Billing City', 'primelink' ),
                'billing_state'     => __( 'Billing State', 'primelink' ),
                'billing_postcode'  => __( 'Billing Postcode', 'primelink' ),
                'billing_country'   => __( 'Billing Country', 'primelink' ),
                'billing_email'     => __( 'Billing Email', 'primelink' ),
                'billing_phone'     => __( 'Billing Phone', 'primelink' ),
                'shipping_first_name' => __( 'Shipping First Name', 'primelink' ),
                'shipping_last_name' => __( 'Shipping Last Name', 'primelink' ),
                'shipping_company'  => __( 'Shipping Company', 'primelink' ),
                'shipping_address_1' => __( 'Shipping Address 1', 'primelink' ),
                'shipping_address_2' => __( 'Shipping Address 2', 'primelink' ),
                'shipping_city'     => __( 'Shipping City', 'primelink' ),
                'shipping_state'    => __( 'Shipping State', 'primelink' ),
                'shipping_postcode' => __( 'Shipping Postcode', 'primelink' ),
                'shipping_country'  => __( 'Shipping Country', 'primelink' ),
            );
        } elseif ( $export_type === 'coupons' ) {
            $labels = array(
                'coupon_id'         => __( 'Coupon ID', 'primelink' ),
                'code'              => __( 'Coupon Code', 'primelink' ),
                'discount_type'     => __( 'Discount Type', 'primelink' ),
                'amount'            => __( 'Discount Amount', 'primelink' ),
                'description'       => __( 'Description', 'primelink' ),
                'date_expires'      => __( 'Date Expires', 'primelink' ),
                'usage_count'       => __( 'Usage Count', 'primelink' ),
                'usage_limit'       => __( 'Usage Limit', 'primelink' ),
                'usage_limit_per_user' => __( 'Usage Limit Per User', 'primelink' ),
                'minimum_amount'    => __( 'Minimum Amount', 'primelink' ),
                'maximum_amount'    => __( 'Maximum Amount', 'primelink' ),
                'individual_use'    => __( 'Individual Use', 'primelink' ),
                'exclude_sale_items' => __( 'Exclude Sale Items', 'primelink' ),
                'free_shipping'     => __( 'Free Shipping', 'primelink' ),
                'product_ids'       => __( 'Product IDs', 'primelink' ),
                'excluded_product_ids' => __( 'Excluded Product IDs', 'primelink' ),
                'product_categories' => __( 'Product Categories', 'primelink' ),
                'excluded_product_categories' => __( 'Excluded Product Categories', 'primelink' ),
                'customer_emails'   => __( 'Customer Emails', 'primelink' ),
                'limit_usage_to_x_items' => __( 'Limit Usage to X Items', 'primelink' ),
                'status'            => __( 'Status', 'primelink' ),
                'date_created'      => __( 'Date Created', 'primelink' ),
                'date_modified'     => __( 'Date Modified', 'primelink' ),
            );
        } elseif ( $export_type === 'reviews' ) {
            $labels = array(
                'review_id'         => __( 'Review ID', 'primelink' ),
                'product_id'        => __( 'Product ID', 'primelink' ),
                'product_sku'       => __( 'Product SKU', 'primelink' ),
                'product_title'     => __( 'Product Title', 'primelink' ),
                'author_name'       => __( 'Author Name', 'primelink' ),
                'author_email'      => __( 'Author Email', 'primelink' ),
                'author_url'        => __( 'Author URL', 'primelink' ),
                'rating'            => __( 'Rating (1-5)', 'primelink' ),
                'title'             => __( 'Review Title', 'primelink' ),
                'content'           => __( 'Review Content', 'primelink' ),
                'status'            => __( 'Status', 'primelink' ),
                'verified_purchase' => __( 'Verified Purchase', 'primelink' ),
                'helpful_votes'     => __( 'Helpful Votes', 'primelink' ),
                'parent_id'         => __( 'Parent Review ID', 'primelink' ),
                'date_created'      => __( 'Date Created', 'primelink' ),
                'date_modified'     => __( 'Date Modified', 'primelink' ),
            );
        }
        
        return $labels[ $field ] ?? ucfirst( str_replace( '_', ' ', $field ) );
    }

    /**
     * Get product data as array.
     *
     * @param int $product_id Product ID.
     * @return array Product data.
     */
    private static function get_product_data( $product_id ) {
        $product = wc_get_product( $product_id );
        if ( ! $product ) {
            return array();
        }

        $categories = wp_get_post_terms( $product_id, 'product_cat', array( 'fields' => 'names' ) );
        $tags = wp_get_post_terms( $product_id, 'product_tag', array( 'fields' => 'names' ) );
        
        // Get featured image
        $featured_image_id = $product->get_image_id();
        $featured_image_url = '';
        if ( $featured_image_id ) {
            $featured_image_url = wp_get_attachment_image_url( $featured_image_id, 'full' );
        }
        
        // Get gallery images
        $gallery_ids = $product->get_gallery_image_ids();
        $gallery_urls = array();
        foreach ( $gallery_ids as $gallery_id ) {
            $gallery_urls[] = wp_get_attachment_image_url( $gallery_id, 'full' );
        }

        return array(
            'id'                => $product_id,
            'title'             => $product->get_name(),
            'sku'               => $product->get_sku(),
            'price'             => $product->get_regular_price(),
            'sale_price'        => $product->get_sale_price(),
            'stock_quantity'    => $product->get_stock_quantity(),
            'stock_status'      => $product->get_stock_status(),
            'manage_stock'      => $product->get_manage_stock() ? 'yes' : 'no',
            'description'       => $product->get_description(),
            'short_description' => $product->get_short_description(),
            'categories'        => implode( ', ', $categories ),
            'tags'              => implode( ', ', $tags ),
            'featured_image'    => $featured_image_url,
            'gallery_images'    => implode( ', ', $gallery_urls ),
            'weight'            => $product->get_weight(),
            'length'            => $product->get_length(),
            'width'             => $product->get_width(),
            'height'            => $product->get_height(),
            'product_type'      => $product->get_type(),
            'status'            => $product->get_status(),
            'date_created'      => $product->get_date_created() ? $product->get_date_created()->date( 'Y-m-d H:i:s' ) : '',
            'date_modified'     => $product->get_date_modified() ? $product->get_date_modified()->date( 'Y-m-d H:i:s' ) : '',
        );
    }

    /**
     * Get order data as array.
     *
     * @param int $order_id Order ID.
     * @return array Order data.
     */
    private static function get_order_data( $order_id ) {
        $order = wc_get_order( $order_id );
        if ( ! $order ) {
            return array();
        }

        // Get line items
        $line_items = array();
        foreach ( $order->get_items() as $item_id => $item ) {
            $product = $item->get_product();
            $line_items[] = array(
                'product_id' => $item->get_product_id(),
                'name'       => $item->get_name(),
                'quantity'   => $item->get_quantity(),
                'total'      => $item->get_total(),
            );
        }
        $line_items_str = '';
        foreach ( $line_items as $item ) {
            $line_items_str .= $item['name'] . ' (x' . $item['quantity'] . ')';
            if ( $item !== end( $line_items ) ) {
                $line_items_str .= '; ';
            }
        }

        return array(
            'order_id'          => $order_id,
            'order_number'      => $order->get_order_number(),
            'status'            => $order->get_status(),
            'date_created'      => $order->get_date_created() ? $order->get_date_created()->date( 'Y-m-d H:i:s' ) : '',
            'date_modified'     => $order->get_date_modified() ? $order->get_date_modified()->date( 'Y-m-d H:i:s' ) : '',
            'date_paid'         => $order->get_date_paid() ? $order->get_date_paid()->date( 'Y-m-d H:i:s' ) : '',
            'date_completed'    => $order->get_date_completed() ? $order->get_date_completed()->date( 'Y-m-d H:i:s' ) : '',
            'customer_id'       => $order->get_customer_id(),
            'customer_email'    => $order->get_billing_email(),
            'customer_name'     => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(),
            'billing_first_name' => $order->get_billing_first_name(),
            'billing_last_name' => $order->get_billing_last_name(),
            'billing_company'   => $order->get_billing_company(),
            'billing_address_1' => $order->get_billing_address_1(),
            'billing_address_2' => $order->get_billing_address_2(),
            'billing_city'      => $order->get_billing_city(),
            'billing_state'    => $order->get_billing_state(),
            'billing_postcode' => $order->get_billing_postcode(),
            'billing_country'  => $order->get_billing_country(),
            'billing_email'    => $order->get_billing_email(),
            'billing_phone'    => $order->get_billing_phone(),
            'shipping_first_name' => $order->get_shipping_first_name(),
            'shipping_last_name' => $order->get_shipping_last_name(),
            'shipping_company' => $order->get_shipping_company(),
            'shipping_address_1' => $order->get_shipping_address_1(),
            'shipping_address_2' => $order->get_shipping_address_2(),
            'shipping_city'    => $order->get_shipping_city(),
            'shipping_state'   => $order->get_shipping_state(),
            'shipping_postcode' => $order->get_shipping_postcode(),
            'shipping_country' => $order->get_shipping_country(),
            'payment_method'   => $order->get_payment_method(),
            'payment_method_title' => $order->get_payment_method_title(),
            'transaction_id'   => $order->get_transaction_id(),
            'order_total'      => $order->get_total(),
            'order_subtotal'   => $order->get_subtotal(),
            'order_tax'        => $order->get_total_tax(),
            'order_shipping'   => $order->get_shipping_total(),
            'order_discount'   => $order->get_total_discount(),
            'currency'         => $order->get_currency(),
            'line_items'       => $line_items_str,
            'customer_note'    => $order->get_customer_note(),
        );
    }

    /**
     * Get customer data as array.
     *
     * @param int $customer_id Customer ID (user ID).
     * @return array Customer data.
     */
    private static function get_customer_data( $customer_id ) {
        $customer = new \WC_Customer( $customer_id );
        if ( ! $customer->get_id() ) {
            return array();
        }

        $user = get_userdata( $customer_id );
        if ( ! $user ) {
            return array();
        }

        return array(
            'customer_id'       => $customer_id,
            'email'             => $customer->get_email(),
            'username'          => $user->user_login,
            'first_name'        => $customer->get_first_name(),
            'last_name'         => $customer->get_last_name(),
            'display_name'      => $user->display_name,
            'role'              => ! empty( $user->roles ) ? implode( ', ', $user->roles ) : '',
            'date_registered'   => $user->user_registered,
            'billing_first_name' => $customer->get_billing_first_name(),
            'billing_last_name' => $customer->get_billing_last_name(),
            'billing_company'   => $customer->get_billing_company(),
            'billing_address_1' => $customer->get_billing_address_1(),
            'billing_address_2' => $customer->get_billing_address_2(),
            'billing_city'      => $customer->get_billing_city(),
            'billing_state'     => $customer->get_billing_state(),
            'billing_postcode'  => $customer->get_billing_postcode(),
            'billing_country'   => $customer->get_billing_country(),
            'billing_email'     => $customer->get_billing_email(),
            'billing_phone'     => $customer->get_billing_phone(),
            'shipping_first_name' => $customer->get_shipping_first_name(),
            'shipping_last_name' => $customer->get_shipping_last_name(),
            'shipping_company'  => $customer->get_shipping_company(),
            'shipping_address_1' => $customer->get_shipping_address_1(),
            'shipping_address_2' => $customer->get_shipping_address_2(),
            'shipping_city'     => $customer->get_shipping_city(),
            'shipping_state'    => $customer->get_shipping_state(),
            'shipping_postcode' => $customer->get_shipping_postcode(),
            'shipping_country'  => $customer->get_shipping_country(),
        );
    }

    /**
     * Get coupon data as array.
     *
     * @param int $coupon_id Coupon ID.
     * @return array Coupon data.
     */
    private static function get_coupon_data( $coupon_id ) {
        $coupon = new \WC_Coupon( $coupon_id );
        if ( ! $coupon->get_id() ) {
            return array();
        }

        // Get product IDs
        $product_ids = $coupon->get_product_ids();
        $excluded_product_ids = $coupon->get_excluded_product_ids();
        $product_categories = $coupon->get_product_categories();
        $excluded_product_categories = $coupon->get_excluded_product_categories();
        $customer_emails = $coupon->get_email_restrictions();

        return array(
            'coupon_id'         => $coupon_id,
            'code'              => $coupon->get_code(),
            'discount_type'     => $coupon->get_discount_type(),
            'amount'            => $coupon->get_amount(),
            'description'       => $coupon->get_description(),
            'date_expires'      => $coupon->get_date_expires() ? $coupon->get_date_expires()->date( 'Y-m-d H:i:s' ) : '',
            'usage_count'       => $coupon->get_usage_count(),
            'usage_limit'       => $coupon->get_usage_limit(),
            'usage_limit_per_user' => $coupon->get_usage_limit_per_user(),
            'minimum_amount'    => $coupon->get_minimum_amount(),
            'maximum_amount'    => $coupon->get_maximum_amount(),
            'individual_use'    => $coupon->get_individual_use() ? 'yes' : 'no',
            'exclude_sale_items' => $coupon->get_exclude_sale_items() ? 'yes' : 'no',
            'free_shipping'     => $coupon->get_free_shipping() ? 'yes' : 'no',
            'product_ids'       => implode( ', ', $product_ids ),
            'excluded_product_ids' => implode( ', ', $excluded_product_ids ),
            'product_categories' => implode( ', ', $product_categories ),
            'excluded_product_categories' => implode( ', ', $excluded_product_categories ),
            'customer_emails'   => implode( ', ', $customer_emails ),
            'limit_usage_to_x_items' => $coupon->get_limit_usage_to_x_items(),
            'status'            => $coupon->get_status(),
            'date_created'      => get_post_field( 'post_date', $coupon_id ),
            'date_modified'     => get_post_field( 'post_modified', $coupon_id ),
        );
    }

    /**
     * Get mapped value from data using mapping config.
     * Uses similar logic to ImportMapper::compute_mapped_value() for consistency.
     *
     * @param array $data   Data (associative array).
     * @param array $config Mapping config.
     * @return string Mapped value.
     */
    private static function get_mapped_value( $data, $config ) {
        $cfg = (array) $config;
        $source = $cfg['source'] ?? '';
        if ( empty( $source ) ) {
            return '';
        }

        // Get raw value from data
        $raw_value = $data[ $source ] ?? '';

        // Parse config if it's a JSON string
        $transform_config = array();
        if ( ! empty( $cfg['config'] ) ) {
            if ( is_string( $cfg['config'] ) ) {
                $transform_config = json_decode( $cfg['config'], true );
            } elseif ( is_array( $cfg['config'] ) ) {
                $transform_config = $cfg['config'];
            }
        }

        // Apply transforms
        if ( ! empty( $transform_config['transforms'] ) ) {
            $raw_value = self::apply_transforms( $raw_value, $transform_config['transforms'] );
        } elseif ( ! empty( $transform_config ) ) {
            // Legacy: if config is directly transform config (not nested)
            $raw_value = self::apply_transforms( $raw_value, $transform_config );
        }

        $value = (string) $raw_value;

        // Apply default value if empty
        if ( ( $value === '' || $value === null ) && ! empty( $transform_config['default'] ) ) {
            $value = (string) $transform_config['default'];
        }

        // Apply prefix/suffix (from config, not transforms)
        if ( ! empty( $transform_config['prefix'] ) ) {
            $value = $transform_config['prefix'] . $value;
        }
        if ( ! empty( $transform_config['suffix'] ) ) {
            $value = $value . $transform_config['suffix'];
        }

        // Apply separator (for multi-value fields)
        if ( ! empty( $transform_config['separator'] ) && is_array( $raw_value ) ) {
            $value = implode( $transform_config['separator'], $raw_value );
        }

        return $value;
    }

    /**
     * Apply transforms to value.
     * Uses the same transform logic as ImportRunner for consistency.
     *
     * @param mixed $raw       Raw value to transform.
     * @param array $transforms Transform configuration.
     * @return mixed Transformed value.
     */
    private static function apply_transforms( $raw, $transforms ) {
        $v = is_array( $raw ) ? implode( '|', $raw ) : (string) $raw;
        $t = (array) $transforms;

        if ( ! empty( $t['trim'] ) ) {
            $v = \PrimeLink\Helpers\StringHelper::trim( $v );
        }

        if ( ! empty( $t['lowercase'] ) ) {
            $v = \PrimeLink\Helpers\StringHelper::lower( $v );
        }

        if ( ! empty( $t['uppercase'] ) ) {
            $v = \PrimeLink\Helpers\StringHelper::upper( $v );
        }

        if ( ! empty( $t['capitalize'] ) ) {
            $v = \PrimeLink\Helpers\StringHelper::capitalize( $v );
        }

        if ( ! empty( $t['title_case'] ) ) {
            $v = \PrimeLink\Helpers\StringHelper::title( $v );
        }

        if ( ! empty( $t['normalize_html'] ) ) {
            $v = wp_strip_all_tags( $v );
            $v = html_entity_decode( $v, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
        }

        if ( ! empty( $t['remove_accents'] ) ) {
            $v = \PrimeLink\Helpers\StringHelper::remove_accents( $v );
        }

        if ( ! empty( $t['slugify'] ) ) {
            $v = \PrimeLink\Helpers\StringHelper::slugify( $v );
        }

        if ( ! empty( $t['extract_numbers'] ) ) {
            $v = preg_replace( '/[^\d.-]/', '', $v );
        }

        if ( ! empty( $t['extract_text'] ) ) {
            $v = preg_replace( '/[\d.-]/', '', $v );
        }

        if ( ! empty( $t['url_encode'] ) ) {
            $v = urlencode( $v );
        }

        if ( ! empty( $t['url_decode'] ) ) {
            $v = urldecode( $v );
        }

        // Number transforms
        if ( ! empty( $t['round'] ) || ! empty( $t['ceil'] ) || ! empty( $t['floor'] ) ) {
            $num = floatval( $v );
            if ( is_numeric( $v ) ) {
                if ( ! empty( $t['round'] ) ) {
                    $decimals = isset( $t['round_decimals'] ) ? absint( $t['round_decimals'] ) : 2;
                    $v = number_format( $num, $decimals, '.', '' );
                } elseif ( ! empty( $t['ceil'] ) ) {
                    $v = (string) ceil( $num );
                } elseif ( ! empty( $t['floor'] ) ) {
                    $v = (string) floor( $num );
                }
            }
        }

        if ( ! empty( $t['number_format'] ) && $t['number_format'] !== '' ) {
            $num = floatval( $v );
            if ( is_numeric( $v ) ) {
                $parts = explode( '.', number_format( $num, 2, '.', '' ) );
                if ( $t['number_format'] === 'comma' ) {
                    $v = number_format( $num, 2, '.', ',' );
                } elseif ( $t['number_format'] === 'dot' ) {
                    $v = number_format( $num, 2, ',', '.' );
                } elseif ( $t['number_format'] === 'space' ) {
                    $v = number_format( $num, 2, '.', ' ' );
                }
            }
        }

        // String operations
        if ( isset( $t['substring_start'] ) && $t['substring_start'] !== '' ) {
            $start = absint( $t['substring_start'] );
            $length = isset( $t['substring_length'] ) && $t['substring_length'] !== '' ? absint( $t['substring_length'] ) : null;
            $v = \PrimeLink\Helpers\StringHelper::slice( $v, $start, $length );
        }

        if ( ! empty( $t['pad_side'] ) && ! empty( $t['pad_length'] ) ) {
            $length = absint( $t['pad_length'] );
            $char = isset( $t['pad_char'] ) && $t['pad_char'] !== '' ? $t['pad_char'][0] : ' ';
            $pad_type = ( $t['pad_side'] === 'left' ) ? STR_PAD_LEFT : ( ( $t['pad_side'] === 'both' ) ? STR_PAD_BOTH : STR_PAD_RIGHT );
            $v = \PrimeLink\Helpers\StringHelper::pad( $v, $length, $char, $pad_type );
        }

        // Date transforms
        if ( ! empty( $t['date_format'] ) && ! empty( $t['date_output'] ) ) {
            $parsed = self::parse_date( $v, $t['date_format'] );
            if ( $parsed ) {
                $v = self::format_date( $parsed, $t['date_output'] );
            }
        }

        // Regex replace
        if ( ! empty( $t['regex_pattern'] ) && $t['regex_pattern'] !== '' ) {
            $pattern = $t['regex_pattern'];
            $replacement = isset( $t['regex_replacement'] ) ? $t['regex_replacement'] : '';
            // Validate regex pattern
            if ( @preg_match( $pattern, '' ) !== false ) {
                $v = preg_replace( $pattern, $replacement, $v );
            }
        }

        // Replace rules (after other transforms)
        if ( ! empty( $t['replace'] ) && is_array( $t['replace'] ) ) {
            foreach ( $t['replace'] as $r ) {
                $from = isset( $r['from'] ) ? (string) $r['from'] : '';
                $to   = isset( $r['to'] ) ? (string) $r['to'] : '';
                if ( $from === '' ) continue;
                $v = str_replace( $from, $to, $v );
            }
        }

        return $v;
    }

    /**
     * Parse date string with given format.
     *
     * @param string $date_string Date string.
     * @param string $format Format (e.g., 'Y-m-d', 'd/m/Y').
     * @return \DateTime|\Carbon\Carbon|false
     */
    private static function parse_date( $date_string, $format ) {
        return \PrimeLink\Helpers\DateHelper::parse( $date_string, $format, false );
    }

    /**
     * Format DateTime object with given format.
     *
     * @param \DateTime|\Carbon\Carbon|int|string $date Date object, timestamp, or string.
     * @param string                              $format Format (e.g., 'Y-m-d', 'd/m/Y').
     * @return string
     */
    private static function format_date( $date, $format ) {
        return \PrimeLink\Helpers\DateHelper::format( $date, $format );
    }

    /**
     * Check if product matches row filters.
     * Uses the same filter logic as ImportRunner::row_matches_filters() for consistency.
     *
     * @param array $product_data Product data (associative array).
     * @param array $row_filters  Row filters.
     * @return bool
     */
    private static function product_matches_filters( $product_data, $row_filters ) {
        if ( empty( $row_filters ) || ! is_array( $row_filters ) ) {
            return true;
        }

        foreach ( $row_filters as $f ) {
            $field = $f['field'] ?? '';
            $op    = $f['op'] ?? '=';
            $value = isset( $f['value'] ) ? (string) $f['value'] : '';

            $left = isset( $item_data[ $field ] ) ? trim( (string) $item_data[ $field ] ) : '';
            $list = self::split_values( $value );

            $match = true;
            switch ( $op ) {
                case '!=':
                    $match = ( $left !== $value );
                    break;
                case 'contains':
                    $match = ( stripos( $left, $value ) !== false );
                    break;
                case 'not_contains':
                    $match = ( stripos( $left, $value ) === false );
                    break;
                case 'in':
                    $match = empty( $list ) ? false : in_array( strtolower( $left ), array_map( 'strtolower', $list ), true );
                    break;
                case 'not_in':
                    $match = empty( $list ) ? true : ! in_array( strtolower( $left ), array_map( 'strtolower', $list ), true );
                    break;
                case 'regex':
                case 'not_regex':
                    $pattern = '/' . str_replace( '/', '\/', $value ) . '/i';
                    $ok = @preg_match( $pattern, $left );
                    $match = $op === 'regex' ? ( $ok === 1 ) : ( $ok === 0 || $ok === false );
                    break;
                case '>':
                    $match = floatval( $left ) > floatval( $value );
                    break;
                case '<':
                    $match = floatval( $left ) < floatval( $value );
                    break;
                case '>=':
                    $match = floatval( $left ) >= floatval( $value );
                    break;
                case '<=':
                    $match = floatval( $left ) <= floatval( $value );
                    break;
                case '=':
                default:
                    $match = ( $left === $value );
                    break;
            }

            if ( ! $match ) {
                return false;
            }
        }

        return true;
    }

    /**
     * Split value string into array (for 'in' and 'not_in' operations).
     *
     * @param string $value Value string (comma, pipe, or semicolon separated).
     * @return array Array of values.
     */
    private static function split_values( $value ) {
        if ( empty( $value ) ) {
            return array();
        }
        $parts = preg_split( '/\s*[,\|;]+\s*/', $value );
        return array_values( array_filter( array_map( 'trim', $parts ) ) );
    }

    /**
     * Get orders preview.
     *
     * @param array $mapping     Field mapping.
     * @param array $filters     Filters.
     * @param array $row_filters Row filters.
     * @param int   $page        Page number.
     * @param int   $per_page    Items per page.
     * @return array
     */
    private static function get_orders_preview( $mapping, $filters, $row_filters, $page, $per_page ) {
        // Build query args
        $args = array(
            'limit'  => $per_page,
            'offset' => ( $page - 1 ) * $per_page,
            'orderby' => 'date',
            'order'   => 'DESC',
        );

        // Apply status filter
        if ( ! empty( $filters['status'] ) && $filters['status'] !== 'any' ) {
            $args['status'] = sanitize_text_field( $filters['status'] );
        }

        // Apply date range filter
        if ( ! empty( $filters['date_from'] ) || ! empty( $filters['date_to'] ) ) {
            $date_query = array();
            if ( ! empty( $filters['date_from'] ) ) {
                $date_query['after'] = sanitize_text_field( $filters['date_from'] );
            }
            if ( ! empty( $filters['date_to'] ) ) {
                $date_query['before'] = sanitize_text_field( $filters['date_to'] );
            }
            $args['date_query'] = $date_query;
        }

        // Get total count (without pagination)
        $count_args = $args;
        unset( $count_args['limit'], $count_args['offset'] );
        $count_args['limit'] = -1;
        $orders_query = wc_get_orders( $count_args );
        $total = count( $orders_query );

        // Get orders with pagination
        $orders = wc_get_orders( $args );

        // Build headers from mapping
        $headers = array();
        $mapping_order = array();
        
        if ( ! empty( $mapping ) && is_array( $mapping ) ) {
            foreach ( $mapping as $column_key => $column_config ) {
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $header = '';
                if ( ! empty( $column_config['config'] ) ) {
                    $config = json_decode( $column_config['config'], true );
                    if ( is_array( $config ) && ! empty( $config['column_header'] ) ) {
                        $header = $config['column_header'];
                    }
                }
                
                if ( empty( $header ) ) {
                    $source = $column_config['source'] ?? '';
                    $header = self::get_field_label( $source, 'orders' );
                }
                
                if ( ! empty( $header ) ) {
                    $headers[] = $header;
                    $mapping_order[] = $column_key;
                }
            }
        }

        // Build rows
        $rows = array();
        foreach ( $orders as $order ) {
            $order_data = self::get_order_data( $order->get_id() );
            
            // Apply row filters if any
            if ( ! empty( $row_filters ) && ! self::item_matches_filters( $order_data, $row_filters ) ) {
                continue;
            }
            
            $row = array();
            foreach ( $mapping_order as $column_key ) {
                $column_config = $mapping[ $column_key ] ?? array();
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $value = self::get_mapped_value( $order_data, $column_config );
                $row[] = $value;
            }
            $rows[] = $row;
        }

        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
            'page'    => $page,
            'per_page' => $per_page,
        );
    }

    /**
     * Get customers preview.
     *
     * @param array $mapping     Field mapping.
     * @param array $filters     Filters.
     * @param array $row_filters Row filters.
     * @param int   $page        Page number.
     * @param int   $per_page    Items per page.
     * @return array
     */
    private static function get_customers_preview( $mapping, $filters, $row_filters, $page, $per_page ) {
        // Build query args
        $args = array(
            'number' => $per_page,
            'offset' => ( $page - 1 ) * $per_page,
            'orderby' => 'registered',
            'order'   => 'DESC',
            'role__in' => array( 'customer', 'subscriber' ), // Only WooCommerce customers
        );

        // Apply role filter
        if ( ! empty( $filters['role'] ) && $filters['role'] !== 'any' ) {
            $args['role'] = sanitize_text_field( $filters['role'] );
        }

        // Get total count
        $count_args = $args;
        $count_args['number'] = -1;
        $count_args['count_total'] = true;
        $user_query = new \WP_User_Query( $count_args );
        $total = $user_query->get_total();

        // Get customers with pagination
        $user_query = new \WP_User_Query( $args );
        $users = $user_query->get_results();

        // Build headers from mapping
        $headers = array();
        $mapping_order = array();
        
        if ( ! empty( $mapping ) && is_array( $mapping ) ) {
            foreach ( $mapping as $column_key => $column_config ) {
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $header = '';
                if ( ! empty( $column_config['config'] ) ) {
                    $config = json_decode( $column_config['config'], true );
                    if ( is_array( $config ) && ! empty( $config['column_header'] ) ) {
                        $header = $config['column_header'];
                    }
                }
                
                if ( empty( $header ) ) {
                    $source = $column_config['source'] ?? '';
                    $header = self::get_field_label( $source, 'customers' );
                }
                
                if ( ! empty( $header ) ) {
                    $headers[] = $header;
                    $mapping_order[] = $column_key;
                }
            }
        }

        // Build rows
        $rows = array();
        foreach ( $users as $user ) {
            $customer_data = self::get_customer_data( $user->ID );
            
            // Apply row filters if any
            if ( ! empty( $row_filters ) && ! self::item_matches_filters( $customer_data, $row_filters ) ) {
                continue;
            }
            
            $row = array();
            foreach ( $mapping_order as $column_key ) {
                $column_config = $mapping[ $column_key ] ?? array();
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $value = self::get_mapped_value( $customer_data, $column_config );
                $row[] = $value;
            }
            $rows[] = $row;
        }

        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
            'page'    => $page,
            'per_page' => $per_page,
        );
    }

    /**
     * Get coupons preview.
     *
     * @param array $mapping     Field mapping.
     * @param array $filters     Filters.
     * @param array $row_filters Row filters.
     * @param int   $page        Page number.
     * @param int   $per_page    Items per page.
     * @return array
     */
    private static function get_coupons_preview( $mapping, $filters, $row_filters, $page, $per_page ) {
        // Build query args
        $args = array(
            'post_type'      => 'shop_coupon',
            'posts_per_page' => $per_page,
            'offset'         => ( $page - 1 ) * $per_page,
            'orderby'        => 'date',
            'order'          => 'DESC',
            'post_status'    => 'any',
        );

        // Apply status filter
        if ( ! empty( $filters['status'] ) && $filters['status'] !== 'any' ) {
            $args['post_status'] = sanitize_text_field( $filters['status'] );
        }

        // Get total count
        $count_args = $args;
        $count_args['posts_per_page'] = -1;
        $count_args['fields'] = 'ids';
        $count_query = new \WP_Query( $count_args );
        $total = $count_query->found_posts;

        // Get coupons with pagination
        $coupons_query = new \WP_Query( $args );
        $coupons = $coupons_query->posts;

        // Build headers from mapping
        $headers = array();
        $mapping_order = array();
        
        if ( ! empty( $mapping ) && is_array( $mapping ) ) {
            foreach ( $mapping as $column_key => $column_config ) {
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $header = '';
                if ( ! empty( $column_config['config'] ) ) {
                    $config = json_decode( $column_config['config'], true );
                    if ( is_array( $config ) && ! empty( $config['column_header'] ) ) {
                        $header = $config['column_header'];
                    }
                }
                
                if ( empty( $header ) ) {
                    $source = $column_config['source'] ?? '';
                    $header = self::get_field_label( $source, 'coupons' );
                }
                
                if ( ! empty( $header ) ) {
                    $headers[] = $header;
                    $mapping_order[] = $column_key;
                }
            }
        }

        // Build rows
        $rows = array();
        foreach ( $coupons as $coupon_post ) {
            $coupon_data = self::get_coupon_data( $coupon_post->ID );
            
            // Apply row filters if any
            if ( ! empty( $row_filters ) && ! self::item_matches_filters( $coupon_data, $row_filters ) ) {
                continue;
            }
            
            $row = array();
            foreach ( $mapping_order as $column_key ) {
                $column_config = $mapping[ $column_key ] ?? array();
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $value = self::get_mapped_value( $coupon_data, $column_config );
                $row[] = $value;
            }
            $rows[] = $row;
        }

        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
            'page'    => $page,
            'per_page' => $per_page,
        );
    }

    /**
     * Get categories preview.
     *
     * @param array $mapping     Field mapping (from step 2).
     * @param array $filters     Category filters (from step 3).
     * @param array $row_filters Row filters (from step 2).
     * @param int   $page        Page number.
     * @param int   $per_page    Items per page.
     * @return array
     */
    private static function get_categories_preview( $mapping, $filters, $row_filters, $page, $per_page ) {
        // Build query args
        $args = array(
            'taxonomy'   => 'product_cat',
            'hide_empty' => false,
            'number'     => $per_page,
            'offset'     => ( $page - 1 ) * $per_page,
            'orderby'    => 'name',
            'order'      => 'ASC',
        );

        // Apply parent filter
        if ( ! empty( $filters['parent'] ) ) {
            $args['parent'] = absint( $filters['parent'] );
        }

        // Get total count
        $count_args = $args;
        $count_args['number'] = 0;
        $count_args['offset'] = 0;
        $count_args['fields'] = 'count';
        $total = wp_count_terms( 'product_cat', $count_args );

        // Get categories
        $categories = get_terms( $args );

        if ( is_wp_error( $categories ) ) {
            $categories = array();
        }

        // Build headers from mapping
        $headers = array();
        $mapping_order = array();
        
        if ( ! empty( $mapping ) && is_array( $mapping ) ) {
            foreach ( $mapping as $column_key => $column_config ) {
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $header = '';
                if ( ! empty( $column_config['config'] ) ) {
                    $config = json_decode( $column_config['config'], true );
                    if ( is_array( $config ) && ! empty( $config['column_header'] ) ) {
                        $header = $config['column_header'];
                    }
                }
                
                if ( empty( $header ) ) {
                    $source = $column_config['source'] ?? '';
                    $header = self::get_field_label( $source, 'categories' );
                }
                
                if ( ! empty( $header ) ) {
                    $headers[] = $header;
                    $mapping_order[] = $column_key;
                }
            }
        }

        // Build rows
        $rows = array();
        foreach ( $categories as $category ) {
            $category_data = self::get_category_data( $category->term_id );
            
            // Apply row filters if any
            if ( ! empty( $row_filters ) && ! self::item_matches_filters( $category_data, $row_filters ) ) {
                continue;
            }
            
            $row = array();
            foreach ( $mapping_order as $column_key ) {
                $column_config = $mapping[ $column_key ] ?? array();
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $value = self::get_mapped_value( $category_data, $column_config );
                $row[] = $value;
            }
            $rows[] = $row;
        }

        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
            'page'    => $page,
            'per_page' => $per_page,
        );
    }

    /**
     * Get category data for export.
     *
     * @param int $term_id Category term ID.
     * @return array Category data.
     */
    private static function get_category_data( $term_id ) {
        $term = get_term( $term_id, 'product_cat' );
        if ( ! $term || is_wp_error( $term ) ) {
            return array();
        }

        $thumbnail_id = get_term_meta( $term_id, 'thumbnail_id', true );
        $thumbnail_url = '';
        if ( $thumbnail_id ) {
            $thumbnail_url = wp_get_attachment_image_url( $thumbnail_id, 'full' );
        }

        $parent_term = $term->parent > 0 ? get_term( $term->parent, 'product_cat' ) : null;
        $parent_name = $parent_term ? $parent_term->name : '';
        $parent_slug = $parent_term ? $parent_term->slug : '';

        return array(
            'category_id'   => $term_id,
            'name'          => $term->name,
            'slug'          => $term->slug,
            'description'   => $term->description,
            'parent'        => $term->parent,
            'parent_name'   => $parent_name,
            'parent_slug'   => $parent_slug,
            'thumbnail'     => $thumbnail_url,
            'thumbnail_id'  => $thumbnail_id,
            'display_type'  => get_term_meta( $term_id, 'display_type', true ),
            'count'         => $term->count,
        );
    }
    
    /**
     * Get tags preview.
     *
     * @param array $mapping     Field mapping (from step 2).
     * @param array $filters     Tag filters (from step 3).
     * @param array $row_filters Row filters (from step 2).
     * @param int   $page        Page number.
     * @param int   $per_page    Items per page.
     * @return array Preview data.
     */
    private static function get_tags_preview( $mapping, $filters, $row_filters, $page, $per_page ) {
        // Build query args
        $args = array(
            'taxonomy'   => 'product_tag',
            'hide_empty' => false,
            'number'     => $per_page,
            'offset'     => ( $page - 1 ) * $per_page,
            'orderby'    => 'name',
            'order'      => 'ASC',
        );
        
        // Apply filters if any
        if ( ! empty( $filters ) ) {
            // Add filter logic here if needed
        }
        
        // Get total count
        $count_args = $args;
        $count_args['number'] = 0;
        $count_args['offset'] = 0;
        $total = wp_count_terms( array(
            'taxonomy' => 'product_tag',
            'hide_empty' => false,
        ) );
        
        if ( is_wp_error( $total ) ) {
            $total = 0;
        }
        
        // Get tags
        $tags = get_terms( $args );
        
        if ( is_wp_error( $tags ) ) {
            $tags = array();
        }
        
        // Build headers from mapping
        $headers = array();
        $mapping_order = array();
        
        foreach ( $mapping as $column_key => $column_config ) {
            $source = $column_config['source'] ?? '';
            
            if ( ! empty( $source ) ) {
                $header = self::get_field_label( $source, 'tags' );
                
                if ( ! empty( $header ) ) {
                    $headers[] = $header;
                    $mapping_order[] = $column_key;
                }
            }
        }
        
        // Build rows
        $rows = array();
        
        foreach ( $tags as $tag ) {
            $tag_data = self::get_tag_data( $tag->term_id );
            
            // Apply row filters if any
            if ( ! empty( $row_filters ) && ! self::item_matches_filters( $tag_data, $row_filters ) ) {
                continue;
            }
            
            $row = array();
            foreach ( $mapping_order as $column_key ) {
                $source = $mapping[ $column_key ]['source'] ?? '';
                $value = $tag_data[ $source ] ?? '';
                $row[] = $value;
            }
            $rows[] = $row;
        }
        
        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
        );
    }
    
    /**
     * Get tag data for export.
     *
     * @param int $term_id Tag term ID.
     * @return array Tag data.
     */
    private static function get_tag_data( $term_id ) {
        $term = get_term( $term_id, 'product_tag' );
        if ( ! $term || is_wp_error( $term ) ) {
            return array();
        }

        $thumbnail_id = get_term_meta( $term_id, 'thumbnail_id', true );
        $thumbnail_url = '';
        if ( $thumbnail_id ) {
            $thumbnail_url = wp_get_attachment_image_url( $thumbnail_id, 'full' );
        }

        return array(
            'tag_id'        => $term_id,
            'name'          => $term->name,
            'slug'          => $term->slug,
            'description'   => $term->description,
            'thumbnail'     => $thumbnail_url,
            'thumbnail_id'  => $thumbnail_id,
            'count'         => $term->count,
        );
    }

    /**
     * Get attributes preview.
     *
     * @param array $mapping     Field mapping (from step 2).
     * @param array $filters     Attribute filters (from step 3).
     * @param array $row_filters Row filters (from step 2).
     * @param int   $page        Page number.
     * @param int   $per_page    Items per page.
     * @return array Preview data.
     */
    private static function get_attributes_preview( $mapping, $filters, $row_filters, $page, $per_page ) {
        // Get all WooCommerce attributes
        $attributes = wc_get_attribute_taxonomies();
        
        // Get total count
        $total = count( $attributes );
        
        // Apply pagination
        $offset = ( $page - 1 ) * $per_page;
        $attributes = array_slice( $attributes, $offset, $per_page );
        
        // Build headers from mapping
        $headers = array();
        $mapping_order = array();
        
        if ( ! empty( $mapping ) && is_array( $mapping ) ) {
            foreach ( $mapping as $column_key => $column_config ) {
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $header = '';
                if ( ! empty( $column_config['config'] ) ) {
                    $config = json_decode( $column_config['config'], true );
                    if ( is_array( $config ) && ! empty( $config['column_header'] ) ) {
                        $header = $config['column_header'];
                    }
                }
                
                if ( empty( $header ) ) {
                    $source = $column_config['source'] ?? '';
                    $header = self::get_field_label( $source, 'attributes' );
                }
                
                if ( ! empty( $header ) ) {
                    $headers[] = $header;
                    $mapping_order[] = $column_key;
                }
            }
        }
        
        // Build rows
        $rows = array();
        foreach ( $attributes as $attribute ) {
            $attribute_data = self::get_attribute_data( $attribute->attribute_id );
            
            // Apply row filters if any
            if ( ! empty( $row_filters ) && ! self::item_matches_filters( $attribute_data, $row_filters ) ) {
                continue;
            }
            
            $row = array();
            foreach ( $mapping_order as $column_key ) {
                $column_config = $mapping[ $column_key ] ?? array();
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $value = self::get_mapped_value( $attribute_data, $column_config );
                $row[] = $value;
            }
            $rows[] = $row;
        }
        
        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
            'page'    => $page,
            'per_page' => $per_page,
        );
    }
    
    /**
     * Get attribute data for export.
     *
     * @param int $attribute_id Attribute ID.
     * @return array Attribute data.
     */
    private static function get_attribute_data( $attribute_id ) {
        $attribute = wc_get_attribute( $attribute_id );
        if ( ! $attribute ) {
            return array();
        }
        
        // Get taxonomy name
        $taxonomy = wc_attribute_taxonomy_name( $attribute->slug );
        
        // Get all terms for this attribute
        $terms = get_terms( array(
            'taxonomy'   => $taxonomy,
            'hide_empty' => false,
        ) );
        
        $terms_data = array();
        $term_names = array();
        $term_slugs = array();
        $term_ids = array();
        
        if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
            foreach ( $terms as $term ) {
                $term_data = array(
                    'id' => $term->term_id,
                    'name' => $term->name,
                    'slug' => $term->slug,
                    'description' => $term->description,
                );
                
                // Get term order if available
                $order = get_term_meta( $term->term_id, 'order', true );
                if ( $order !== '' ) {
                    $term_data['order'] = absint( $order );
                }
                
                // Get term color if available (for swatches)
                $color = get_term_meta( $term->term_id, 'product_attribute_color', true );
                if ( ! empty( $color ) ) {
                    $term_data['color'] = $color;
                }
                
                $terms_data[] = $term_data;
                $term_names[] = $term->name;
                $term_slugs[] = $term->slug;
                $term_ids[] = $term->term_id;
            }
        }
        
        return array(
            'attribute_id'  => $attribute_id,
            'name'          => $attribute->name,
            'slug'          => $attribute->slug,
            'taxonomy'      => $taxonomy, // Full taxonomy name (e.g. "pa_color")
            'type'          => $attribute->type,
            'order_by'      => $attribute->order_by,
            'has_archives'  => $attribute->has_archives ? '1' : '0',
            'term_count'    => count( $terms_data ),
            'term_names'    => implode( ', ', $term_names ), // Comma-separated term names
            'term_slugs'    => implode( ', ', $term_slugs ), // Comma-separated term slugs
            'term_ids'      => implode( ', ', $term_ids ), // Comma-separated term IDs
            'terms'         => wp_json_encode( $terms_data ), // Full JSON with all term details
        );
    }

    /**
     * Get variations preview.
     *
     * @param array $mapping     Field mapping.
     * @param array $filters     Variation filters.
     * @param array $row_filters Row filters.
     * @param int   $page        Page number.
     * @param int   $per_page    Items per page.
     * @return array Preview data.
     */
    private static function get_variations_preview( $mapping, $filters, $row_filters, $page, $per_page ) {
        // Get all variations
        $args = array(
            'post_type'      => 'product_variation',
            'posts_per_page' => -1,
            'post_status'    => 'any',
            'orderby'        => 'ID',
            'order'          => 'ASC',
        );
        
        // Apply parent product filter
        if ( ! empty( $filters['parent_id'] ) ) {
            $args['post_parent'] = absint( $filters['parent_id'] );
        }
        
        // Apply stock status filter
        if ( ! empty( $filters['stock_status'] ) ) {
            $args['meta_query'][] = array(
                'key'   => '_stock_status',
                'value' => sanitize_text_field( $filters['stock_status'] ),
            );
        }
        
        $variations_query = new \WP_Query( $args );
        $variations = $variations_query->posts;
        
        // Apply row filters and pagination
        $filtered_variations = array();
        foreach ( $variations as $variation_post ) {
            $variation_data = self::get_variation_data( $variation_post->ID );
            if ( empty( $row_filters ) || self::item_matches_filters( $variation_data, $row_filters ) ) {
                $filtered_variations[] = $variation_data;
            }
        }
        
        $total = count( $filtered_variations );
        $offset = ( $page - 1 ) * $per_page;
        $paginated_variations = array_slice( $filtered_variations, $offset, $per_page );
        
        // Build headers from mapping
        $headers = array();
        foreach ( $mapping as $column_key => $column_config ) {
            $label = self::get_field_label( $column_key, 'variations' );
            $headers[] = $label;
        }
        
        // Build rows
        $rows = array();
        foreach ( $paginated_variations as $variation_data ) {
            $row = array();
            foreach ( $mapping as $column_key => $column_config ) {
                $value = self::get_mapped_value( $variation_data, $column_config );
                $row[] = $value;
            }
            $rows[] = $row;
        }
        
        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
        );
    }

    /**
     * Get variation data for export.
     *
     * @param int $variation_id Variation ID.
     * @return array Variation data.
     */
    private static function get_variation_data( $variation_id ) {
        $variation = wc_get_product( $variation_id );
        if ( ! $variation || ! $variation->is_type( 'variation' ) ) {
            return array();
        }
        
        $parent_id = $variation->get_parent_id();
        $parent = wc_get_product( $parent_id );
        
        // Get gallery images
        $gallery_ids = get_post_meta( $variation_id, '_variation_gallery', true );
        $gallery_urls = array();
        $gallery_ids_array = array();
        if ( ! empty( $gallery_ids ) ) {
            $gallery_ids_array = explode( ',', $gallery_ids );
            foreach ( $gallery_ids_array as $img_id ) {
                $img_url = wp_get_attachment_image_url( $img_id, 'full' );
                if ( $img_url ) {
                    $gallery_urls[] = $img_url;
                }
            }
        }
        
        // Get attributes
        $attributes = $variation->get_attributes();
        $attributes_json = wp_json_encode( $attributes );
        
        // Get image
        $image_id = $variation->get_image_id();
        $image_url = '';
        if ( $image_id ) {
            $image_url = wp_get_attachment_image_url( $image_id, 'full' );
        }
        
        return array(
            'variation_id'      => $variation_id,
            'parent_id'         => $parent_id,
            'parent_sku'        => $parent ? $parent->get_sku() : '',
            'parent_title'      => $parent ? $parent->get_name() : '',
            'sku'               => $variation->get_sku(),
            'regular_price'     => $variation->get_regular_price(),
            'sale_price'        => $variation->get_sale_price(),
            'stock_quantity'    => $variation->get_stock_quantity(),
            'stock_status'      => $variation->get_stock_status(),
            'manage_stock'      => $variation->get_manage_stock() ? 'yes' : 'no',
            'backorders'        => $variation->get_backorders(),
            'weight'            => $variation->get_weight(),
            'length'            => $variation->get_length(),
            'width'             => $variation->get_width(),
            'height'            => $variation->get_height(),
            'image'             => $image_url,
            'image_id'          => $image_id,
            'gallery'           => implode( ', ', $gallery_urls ),
            'gallery_ids'       => implode( ', ', $gallery_ids_array ),
            'description'       => $variation->get_description(),
            'short_description' => $variation->get_short_description(),
            'attributes'        => $attributes_json,
            'tax_class'         => $variation->get_tax_class(),
            'status'            => $variation->get_status(),
            'date_created'      => $variation->get_date_created() ? $variation->get_date_created()->date( 'Y-m-d H:i:s' ) : '',
            'date_modified'     => $variation->get_date_modified() ? $variation->get_date_modified()->date( 'Y-m-d H:i:s' ) : '',
        );
    }

    /**
     * Get downloads preview.
     *
     * @param array $mapping     Field mapping.
     * @param array $filters     Download filters.
     * @param array $row_filters Row filters.
     * @param int   $page        Page number.
     * @param int   $per_page    Items per page.
     * @return array Preview data.
     */
    private static function get_downloads_preview( $mapping, $filters, $row_filters, $page, $per_page ) {
        // Get all downloadable products
        $args = array(
            'post_type'      => 'product',
            'posts_per_page' => -1,
            'post_status'    => 'any',
            'meta_query'     => array(
                array(
                    'key'   => '_downloadable',
                    'value' => 'yes',
                ),
            ),
            'fields'         => 'ids',
        );

        // Apply product filter
        if ( ! empty( $filters['product_id'] ) && absint( $filters['product_id'] ) > 0 ) {
            $args['p'] = absint( $filters['product_id'] );
        }

        // Apply downloadable_only filter (default true)
        if ( isset( $filters['downloadable_only'] ) && ! $filters['downloadable_only'] ) {
            // If false, remove the meta_query filter
            unset( $args['meta_query'] );
        }

        $product_ids = get_posts( $args );
        $all_downloads = array();
        
        foreach ( $product_ids as $product_id ) {
            $product = wc_get_product( $product_id );
            if ( ! $product || ! $product->is_downloadable() ) {
                continue;
            }
            
            $downloads = $product->get_downloads();
            if ( empty( $downloads ) ) {
                continue;
            }
            
            // Create a row for each download
            foreach ( $downloads as $download_key => $download ) {
                $download_data = self::get_download_data( $product_id, $download_key, $download );
                
                // Apply row filters if any
                if ( ! empty( $row_filters ) && ! self::item_matches_filters( $download_data, $row_filters ) ) {
                    continue;
                }
                
                $all_downloads[] = $download_data;
            }
        }
        
        $total = count( $all_downloads );
        $offset = ( $page - 1 ) * $per_page;
        $paginated_downloads = array_slice( $all_downloads, $offset, $per_page );
        
        // Build headers from mapping
        $headers = array();
        $mapping_order = array();
        
        if ( ! empty( $mapping ) && is_array( $mapping ) ) {
            foreach ( $mapping as $column_key => $column_config ) {
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $header = '';
                if ( ! empty( $column_config['config'] ) ) {
                    $config = json_decode( $column_config['config'], true );
                    if ( is_array( $config ) && ! empty( $config['column_header'] ) ) {
                        $header = $config['column_header'];
                    }
                }
                
                if ( empty( $header ) ) {
                    $source = $column_config['source'] ?? '';
                    $header = self::get_field_label( $source, 'downloads' );
                }
                
                if ( ! empty( $header ) ) {
                    $headers[] = $header;
                    $mapping_order[] = $column_key;
                }
            }
        }
        
        // Build rows
        $rows = array();
        foreach ( $paginated_downloads as $download_data ) {
            $row = array();
            foreach ( $mapping_order as $column_key ) {
                $column_config = $mapping[ $column_key ] ?? array();
                if ( is_string( $column_config ) ) {
                    $column_config = array( 'source' => $column_config );
                }
                
                $value = self::get_mapped_value( $download_data, $column_config );
                $row[] = $value;
            }
            $rows[] = $row;
        }
        
        return array(
            'headers' => $headers,
            'rows'    => $rows,
            'total'   => $total,
            'page'    => $page,
            'per_page' => $per_page,
        );
    }

    /**
     * Get download data for export.
     *
     * @param int    $product_id  Product ID.
     * @param string $download_key Download key.
     * @param object $download    WC_Product_Download object.
     * @return array Download data.
     */
    private static function get_download_data( $product_id, $download_key, $download ) {
        $product = wc_get_product( $product_id );
        if ( ! $product ) {
            return array();
        }
        
        // Get download limit and expiry from product meta
        $download_limit = get_post_meta( $product_id, '_download_limit', true );
        $download_expiry = get_post_meta( $product_id, '_download_expiry', true );
        
        return array(
            'product_id'        => $product_id,
            'product_sku'       => $product->get_sku(),
            'product_title'     => $product->get_name(),
            'download_id'       => $download_key,
            'file_name'         => $download->get_name(),
            'file_url'          => $download->get_file(),
            'download_limit'    => $download_limit ? absint( $download_limit ) : '',
            'download_expiry'   => $download_expiry ? absint( $download_expiry ) : '',
        );
    }

    /**
     * Start export.
     *
     * @param int   $export_id Export ID.
     * @param array $draft     Draft data.
     * @param bool  $background Whether to run in background.
     * @return array Result.
     */
    public static function start( $export_id, $draft, $background = false ) {
        $export_type = $draft['type'] ?? 'products';
        
        // Get data from step 2 (mapping) and step 3 (options)
        $step2_data = $draft['step_data'][2] ?? array();
        $step3_data = $draft['step_data'][3] ?? array();
        
        $mapping     = $step2_data['mapping'] ?? array();
        $row_filters = $step2_data['row_filters'] ?? array();
        $filters     = $step3_data['filters'] ?? array();
        $format      = $step3_data['format'] ?? 'csv';
        $format_opts = $step3_data;
        $destination = $step3_data['destination'] ?? 'download';

        // Initialize state
        $state = array(
            'status'     => 'running',
            'progress'   => 0,
            'current'    => 0,
            'total'      => 0,
            'file_path'  => '',
            'file_url'   => '',
            'log'        => array(),
        );

        self::save_state( $export_id, $state );

        // Start export in background or process immediately
        $settings = self::get_settings();
        if ( $background && ( $settings['cron_mode'] ?? 'wp' ) !== 'external' ) {
            // Schedule background processing
            if ( ! wp_next_scheduled( 'mt_primelink_export_tick', array( $export_id ) ) ) {
                wp_schedule_single_event( time() + 1, 'mt_primelink_export_tick', array( $export_id ) );
            }
        } else {
            // Process immediately
            self::process_export( $export_id, $export_type, $mapping, $filters, $row_filters, $format, $format_opts, $destination );
        }

        return array(
            'success' => true,
            'export_id' => $export_id,
        );
    }

    /**
     * Get settings.
     *
     * @return array Settings.
     */
    private static function get_settings() {
        $settings = get_option( 'mt_primelink_settings', array() );
        if ( ! is_array( $settings ) ) {
            $settings = array();
        }
        $defaults = array(
            'cron_mode'  => 'wp',
            'cron_token' => '',
        );
        return array_merge( $defaults, $settings );
    }

    /**
     * Tick export processing (for background).
     *
     * @param int $export_id Export ID.
     * @return void
     */
    public static function tick( $export_id ) {
        $state = self::get_state( $export_id );
        if ( empty( $state ) || ( $state['status'] ?? '' ) !== 'running' ) {
            return;
        }

        $drafts = get_option( 'mt_primelink_export_drafts', array() );
        $draft = $drafts[ $export_id ] ?? null;
        if ( empty( $draft ) ) {
            $state['status'] = 'error';
            $state['log'][] = 'Draft not found.';
            self::save_state( $export_id, $state );
            return;
        }

        $export_type = $draft['type'] ?? 'products';
        $step2_data = $draft['step_data'][2] ?? array();
        $step3_data = $draft['step_data'][3] ?? array();
        
        $mapping     = $step2_data['mapping'] ?? array();
        $row_filters = $step2_data['row_filters'] ?? array();
        $filters     = $step3_data['filters'] ?? array();
        $format      = $step3_data['format'] ?? 'csv';
        $format_opts = $step3_data;
        $destination = $step3_data['destination'] ?? 'download';

        // Process export in chunks
        self::process_export( $export_id, $export_type, $mapping, $filters, $row_filters, $format, $format_opts, $destination );

        // Check if we need to continue
        $current_state = self::get_state( $export_id );
        if ( ( $current_state['status'] ?? '' ) === 'running' && ! empty( $current_state['paused'] ) ) {
            // Paused, don't schedule next tick
            return;
        }

        if ( ( $current_state['status'] ?? '' ) === 'running' ) {
            // Schedule next tick
            $settings = self::get_settings();
            if ( ( $settings['cron_mode'] ?? 'wp' ) !== 'external' ) {
                if ( ! wp_next_scheduled( 'mt_primelink_export_tick', array( $export_id ) ) ) {
                    wp_schedule_single_event( time() + 1, 'mt_primelink_export_tick', array( $export_id ) );
                }
            }
        }
    }

    /**
     * Process export.
     *
     * @param int    $export_id   Export ID.
     * @param string $export_type Export type.
     * @param array  $mapping     Field mapping.
     * @param array  $filters     Product filters.
     * @param array  $row_filters Row filters.
     * @param string $format      Format (csv, xml, xlsx).
     * @param array  $format_opts Format options (delimiter, encoding, etc.).
     * @param string $destination Destination (download, ftp, etc.).
     * @return void
     */
    private static function process_export( $export_id, $export_type, $mapping, $filters, $row_filters, $format, $format_opts, $destination ) {
        $state = self::get_state( $export_id );
        
        // Ensure status is running
        $state['status'] = 'running';
        $state['paused'] = false;
        self::save_state( $export_id, $state );
        
        try {
            // Get items based on export type and filters
            $items = array();
            $total = 0;
            
            switch ( $export_type ) {
                case 'products':
                    $args = array(
                        'post_type'      => 'product',
                        'posts_per_page' => -1,
                        'post_status'    => 'any',
                    );

                    // Apply filters
                    if ( ! empty( $filters['categories'] ) ) {
                        $args['tax_query'][] = array(
                            'taxonomy' => 'product_cat',
                            'field'    => 'term_id',
                            'terms'    => array_map( 'absint', (array) $filters['categories'] ),
                        );
                    }

                    if ( ! empty( $filters['tags'] ) ) {
                        if ( ! isset( $args['tax_query'] ) ) {
                            $args['tax_query'] = array();
                        }
                        $args['tax_query'][] = array(
                            'taxonomy' => 'product_tag',
                            'field'    => 'term_id',
                            'terms'    => array_map( 'absint', (array) $filters['tags'] ),
                        );
                    }

                    if ( ! empty( $filters['status'] ) && $filters['status'] !== 'any' ) {
                        $args['post_status'] = sanitize_text_field( $filters['status'] );
                    }

                    // Apply limit if set
                    if ( ! empty( $format_opts['limit'] ) && absint( $format_opts['limit'] ) > 0 ) {
                        $args['posts_per_page'] = absint( $format_opts['limit'] );
                    }

                    $items = get_posts( $args );
                    $total = count( $items );
                    break;

                case 'orders':
                    $args = array(
                        'limit'  => -1,
                        'orderby' => 'date',
                        'order'   => 'DESC',
                    );

                    // Apply status filter
                    if ( ! empty( $filters['status'] ) && $filters['status'] !== 'any' ) {
                        $args['status'] = sanitize_text_field( $filters['status'] );
                    }

                    // Apply date range filter
                    if ( ! empty( $filters['date_from'] ) || ! empty( $filters['date_to'] ) ) {
                        $date_query = array();
                        if ( ! empty( $filters['date_from'] ) ) {
                            $date_query['after'] = sanitize_text_field( $filters['date_from'] );
                        }
                        if ( ! empty( $filters['date_to'] ) ) {
                            $date_query['before'] = sanitize_text_field( $filters['date_to'] );
                        }
                        $args['date_query'] = $date_query;
                    }

                    // Apply limit if set
                    if ( ! empty( $format_opts['limit'] ) && absint( $format_opts['limit'] ) > 0 ) {
                        $args['limit'] = absint( $format_opts['limit'] );
                    }

                    $items = wc_get_orders( $args );
                    $total = count( $items );
                    break;

                case 'customers':
                    $args = array(
                        'number' => -1,
                        'orderby' => 'registered',
                        'order'   => 'DESC',
                        'role__in' => array( 'customer', 'subscriber' ),
                    );

                    // Apply role filter
                    if ( ! empty( $filters['role'] ) && $filters['role'] !== 'any' ) {
                        $args['role'] = sanitize_text_field( $filters['role'] );
                    }

                    // Apply limit if set
                    if ( ! empty( $format_opts['limit'] ) && absint( $format_opts['limit'] ) > 0 ) {
                        $args['number'] = absint( $format_opts['limit'] );
                    }

                    $user_query = new \WP_User_Query( $args );
                    $items = $user_query->get_results();
                    $total = count( $items );
                    break;

                case 'categories':
                    $args = array(
                        'taxonomy'   => 'product_cat',
                        'hide_empty' => false,
                        'number'     => 0,
                        'orderby'    => 'name',
                        'order'      => 'ASC',
                    );
                case 'tags':
                    $args = array(
                        'taxonomy'   => 'product_tag',
                        'hide_empty' => false,
                        'number'     => 0,
                        'orderby'    => 'name',
                        'order'      => 'ASC',
                    );

                    // Apply parent filter
                    if ( ! empty( $filters['parent'] ) ) {
                        $args['parent'] = absint( $filters['parent'] );
                    }

                    // Apply limit if set
                    if ( ! empty( $format_opts['limit'] ) && absint( $format_opts['limit'] ) > 0 ) {
                        $args['number'] = absint( $format_opts['limit'] );
                    }
                    break;

                case 'attributes':
                    // Get all WooCommerce attributes
                    $attributes = wc_get_attribute_taxonomies();
                    
                    // Apply limit if set
                    if ( ! empty( $format_opts['limit'] ) && absint( $format_opts['limit'] ) > 0 ) {
                        $attributes = array_slice( $attributes, 0, absint( $format_opts['limit'] ) );
                    }
                    
                    $items = array();
                    foreach ( $attributes as $attribute ) {
                        $items[] = self::get_attribute_data( $attribute->attribute_id );
                    }
                    $total = count( $items );
                    break;

                case 'variations':
                    // Get all variations
                    $args = array(
                        'post_type'      => 'product_variation',
                        'posts_per_page' => -1,
                        'post_status'    => 'any',
                        'orderby'        => 'ID',
                        'order'          => 'ASC',
                    );
                    
                    // Apply parent product filter
                    if ( ! empty( $filters['parent_id'] ) ) {
                        $args['post_parent'] = absint( $filters['parent_id'] );
                    }
                    
                    // Apply stock status filter
                    if ( ! empty( $filters['stock_status'] ) ) {
                        $args['meta_query'][] = array(
                            'key'   => '_stock_status',
                            'value' => sanitize_text_field( $filters['stock_status'] ),
                        );
                    }
                    
                    $variations_query = new \WP_Query( $args );
                    $variations = $variations_query->posts;
                    
                    // Apply limit if set
                    if ( ! empty( $format_opts['limit'] ) && absint( $format_opts['limit'] ) > 0 ) {
                        $variations = array_slice( $variations, 0, absint( $format_opts['limit'] ) );
                    }
                    
                    $items = array();
                    foreach ( $variations as $variation_post ) {
                        $items[] = self::get_variation_data( $variation_post->ID );
                    }
                    $total = count( $items );
                    break;

                case 'coupons':
                    $args = array(
                        'post_type'      => 'shop_coupon',
                        'posts_per_page' => -1,
                        'orderby'        => 'date',
                        'order'          => 'DESC',
                        'post_status'    => 'any',
                    );

                    // Apply status filter
                    if ( ! empty( $filters['status'] ) && $filters['status'] !== 'any' ) {
                        $args['post_status'] = sanitize_text_field( $filters['status'] );
                    }

                    // Apply limit if set
                    if ( ! empty( $format_opts['limit'] ) && absint( $format_opts['limit'] ) > 0 ) {
                        $args['posts_per_page'] = absint( $format_opts['limit'] );
                    }

                    $items = get_posts( $args );
                    $total = count( $items );
                    break;

                case 'reviews':
                    $args = array(
                        'type' => 'review',
                        'status' => 'all',
                        'number' => -1,
                    );

                    // Apply product filter
                    if ( ! empty( $filters['product_id'] ) && absint( $filters['product_id'] ) > 0 ) {
                        $args['post_id'] = absint( $filters['product_id'] );
                    }

                    // Apply rating filter
                    if ( ! empty( $filters['rating'] ) && absint( $filters['rating'] ) > 0 ) {
                        $args['meta_query'] = array(
                            array(
                                'key' => 'rating',
                                'value' => absint( $filters['rating'] ),
                                'compare' => '=',
                            ),
                        );
                    }

                    // Apply status filter
                    if ( ! empty( $filters['status'] ) ) {
                        $status = sanitize_text_field( $filters['status'] );
                        if ( $status === 'approved' ) {
                            $args['status'] = 'approve';
                        } elseif ( in_array( $status, array( 'pending', 'spam', 'trash' ), true ) ) {
                            $args['status'] = $status;
                        }
                    }

                    // Apply verified purchase filter
                    if ( ! empty( $filters['verified_only'] ) ) {
                        if ( ! isset( $args['meta_query'] ) ) {
                            $args['meta_query'] = array();
                        }
                        $args['meta_query'][] = array(
                            'key' => 'verified',
                            'value' => 'yes',
                            'compare' => '=',
                        );
                    }

                    $reviews = get_comments( $args );

                    // Apply limit if set
                    if ( ! empty( $format_opts['limit'] ) && absint( $format_opts['limit'] ) > 0 ) {
                        $reviews = array_slice( $reviews, 0, absint( $format_opts['limit'] ) );
                    }

                    $items = array();
                    foreach ( $reviews as $review ) {
                    $items[] = self::get_review_data( $review->comment_ID );
                }
                $total = count( $items );
                break;

                case 'downloads':
                    // Get all downloadable products
                    $args = array(
                        'post_type'      => 'product',
                        'posts_per_page' => -1,
                        'post_status'    => 'any',
                        'meta_query'     => array(
                            array(
                                'key'   => '_downloadable',
                                'value' => 'yes',
                            ),
                        ),
                        'fields'         => 'ids',
                    );

                    // Apply product filter
                    if ( ! empty( $filters['product_id'] ) && absint( $filters['product_id'] ) > 0 ) {
                        $args['p'] = absint( $filters['product_id'] );
                    }

                    // Apply downloadable_only filter (default true)
                    if ( isset( $filters['downloadable_only'] ) && ! $filters['downloadable_only'] ) {
                        // If false, remove the meta_query filter
                        unset( $args['meta_query'] );
                    }

                    $product_ids = get_posts( $args );
                    $items = array();
                    
                    foreach ( $product_ids as $product_id ) {
                        $product = wc_get_product( $product_id );
                        if ( ! $product || ! $product->is_downloadable() ) {
                            continue;
                        }
                        
                        $downloads = $product->get_downloads();
                        if ( empty( $downloads ) ) {
                            continue;
                        }
                        
                        // Create a row for each download
                        foreach ( $downloads as $download_key => $download ) {
                            $items[] = self::get_download_data( $product_id, $download_key, $download );
                        }
                    }
                    
                    // Apply limit if set
                    if ( ! empty( $format_opts['limit'] ) && absint( $format_opts['limit'] ) > 0 ) {
                        $items = array_slice( $items, 0, absint( $format_opts['limit'] ) );
                    }
                    
                    $total = count( $items );
                    break;
            }
            
            $state['total'] = $total;
            self::save_state( $export_id, $state );

            if ( $total === 0 ) {
                $state['status'] = 'completed';
                $state['log'][] = sprintf( 'No %s found matching filters.', $export_type );
                self::save_state( $export_id, $state );
                return;
            }

            // Build headers from mapping
            $headers = array();
            $mapping_order = array();
            
            if ( ! empty( $mapping ) && is_array( $mapping ) ) {
                foreach ( $mapping as $column_key => $column_config ) {
                    if ( is_string( $column_config ) ) {
                        $column_config = array( 'source' => $column_config );
                    }
                    
                    $header = '';
                    if ( ! empty( $column_config['config'] ) ) {
                        $config = json_decode( $column_config['config'], true );
                        if ( is_array( $config ) && ! empty( $config['column_header'] ) ) {
                            $header = $config['column_header'];
                        }
                    }
                    
                    if ( empty( $header ) ) {
                        $source = $column_config['source'] ?? '';
                        $header = self::get_field_label( $source, $export_type );
                    }
                    
                    if ( ! empty( $header ) ) {
                        $headers[] = $header;
                        $mapping_order[] = $column_key;
                    }
                }
            }

            // Build rows
            $rows = array();
            $current = 0;
            
            foreach ( $items as $item ) {
                // Get item data based on type
                $item_data = array();
                $item_id = 0;
                
                switch ( $export_type ) {
                    case 'products':
                        $item_id = $item->ID;
                        $item_data = self::get_product_data( $item_id );
                        break;
                    case 'orders':
                        $item_id = $item->get_id();
                        $item_data = self::get_order_data( $item_id );
                        break;
                    case 'customers':
                        $item_id = $item->ID;
                        $item_data = self::get_customer_data( $item_id );
                        break;
                    case 'categories':
                        $item_id = is_object( $item ) ? $item->term_id : ( is_array( $item ) ? $item['term_id'] : $item );
                        $item_data = self::get_category_data( $item_id );
                        break;
                    case 'coupons':
                        $item_id = $item->ID;
                        $item_data = self::get_coupon_data( $item_id );
                        break;
                    case 'categories':
                        $item_id = $item->term_id;
                        $item_data = self::get_category_data( $item_id );
                        break;
                    case 'tags':
                        $item_id = $item->term_id;
                        $item_data = self::get_tag_data( $item_id );
                        break;
                    case 'attributes':
                        // For attributes, $item is already an array (from get_attribute_data)
                        $item_data = $item;
                        $item_id = $item_data['attribute_id'] ?? 0;
                        break;
                    case 'variations':
                        // For variations, $item is already an array (from get_variation_data)
                        $item_data = $item;
                        $item_id = $item_data['variation_id'] ?? 0;
                        break;
                }
                
                // Apply row filters
                if ( ! empty( $row_filters ) && ! self::item_matches_filters( $item_data, $row_filters ) ) {
                    continue;
                }
                
                $row = array();
                foreach ( $mapping_order as $column_key ) {
                    $column_config = $mapping[ $column_key ] ?? array();
                    if ( is_string( $column_config ) ) {
                        $column_config = array( 'source' => $column_config );
                    }
                    
                    $value = self::get_mapped_value( $item_data, $column_config );
                    $row[] = $value;
                }
                $rows[] = $row;
                
                $current++;
                $progress = $total > 0 ? round( ( $current / $total ) * 100 ) : 0;
                
                $state['current'] = $current;
                $state['progress'] = $progress;
                
                // Check for pause every 10 items
                if ( $current % 10 === 0 ) {
                    $current_state = self::get_state( $export_id );
                    if ( ! empty( $current_state['paused'] ) ) {
                        $state['status'] = 'paused';
                        self::save_state( $export_id, $state );
                        return;
                    }
                    self::save_state( $export_id, $state );
                }
            }

            // Generate file
            $upload_dir = wp_upload_dir();
            $export_dir = $upload_dir['basedir'] . '/primelink-exports';
            if ( ! \PrimeLink\Helpers\FilesystemHelper::directory_exists( $export_dir ) ) {
                \PrimeLink\Helpers\FilesystemHelper::create_directory( $export_dir );
            }

            $file_name = 'export-' . $export_id . '-' . time() . '.' . $format;
            $file_path = $export_dir . '/' . $file_name;
            $file_url  = $upload_dir['baseurl'] . '/primelink-exports/' . $file_name;

            // Write file based on format
            if ( $format === 'csv' ) {
                self::write_csv_file( $file_path, $headers, $rows, $format_opts );
            } elseif ( $format === 'xml' ) {
                self::write_xml_file( $file_path, $headers, $rows );
            } elseif ( $format === 'xlsx' ) {
                self::write_xlsx_file( $file_path, $headers, $rows );
            }

            // Upload to destination if not download
            if ( $destination !== 'download' ) {
                $upload_result = ExportDestinationService::upload( $file_path, $destination, $format_opts );
                if ( $upload_result['success'] ) {
                    $state['log'][] = $upload_result['message'] ?? 'File uploaded successfully.';
                } else {
                    $state['log'][] = 'Upload failed: ' . ( $upload_result['error'] ?? 'Unknown error' );
                    // Still mark as completed, file is saved locally
                }
            }

            $state['status'] = 'completed';
            $state['progress'] = 100;
            $state['file_path'] = $file_path;
            $state['file_url'] = $file_url;
            $state['log'][] = sprintf( 'Export completed. %d %s exported.', $current, $export_type );
            self::save_state( $export_id, $state );

            // Update next_run for scheduled exports (especially important for external cron)
            $drafts = get_option( 'mt_primelink_export_drafts', array() );
            if ( isset( $drafts[ $export_id ] ) ) {
                $schedule = $drafts[ $export_id ]['step_data'][3]['schedule'] ?? array();
                $enabled = ! empty( $schedule['enabled'] );
                if ( $enabled ) {
                    // Update next_run when export finishes to ensure proper scheduling
                    $drafts[ $export_id ]['step_data'][3]['schedule'] = self::bump_next_run( $schedule );
                    update_option( 'mt_primelink_export_drafts', $drafts, false );
                }
            }

        } catch ( \Exception $e ) {
            $state['status'] = 'error';
            $state['log'][] = 'Error: ' . $e->getMessage();
            self::save_state( $export_id, $state );
        }
    }

    /**
     * Write CSV file.
     *
     * @param string $file_path File path.
     * @param array  $headers   Headers.
     * @param array  $rows      Rows.
     * @param array  $opts      Options (delimiter, encoding, include_headers).
     * @return void
     */
    private static function write_csv_file( $file_path, $headers, $rows, $opts ) {
        $delimiter = $opts['csv_delimiter'] ?? ',';
        $encoding = $opts['csv_encoding'] ?? 'UTF-8';
        $include_headers = ! isset( $opts['include_headers'] ) || $opts['include_headers'];
        
        $fp = fopen( $file_path, 'w' );
        
        if ( $encoding === 'UTF-8-BOM' ) {
            fwrite( $fp, "\xEF\xBB\xBF" );
        }
        
        if ( $include_headers && ! empty( $headers ) ) {
            fputcsv( $fp, $headers, $delimiter );
        }
        
        foreach ( $rows as $row ) {
            fputcsv( $fp, $row, $delimiter );
        }
        
        fclose( $fp );
    }

    /**
     * Write XML file.
     *
     * @param string $file_path File path.
     * @param array  $headers   Headers.
     * @param array  $rows      Rows.
     * @return void
     */
    private static function write_xml_file( $file_path, $headers, $rows, $export_type = 'products' ) {
        $root_name = $export_type; // products, orders, customers, coupons, categories
        // Handle special case for categories, tags, and attributes
        if ( $export_type === 'categories' ) {
            $item_name = 'category';
        } elseif ( $export_type === 'tags' ) {
            $item_name = 'tag';
        } elseif ( $export_type === 'attributes' ) {
            $item_name = 'attribute';
        } elseif ( $export_type === 'variations' ) {
            $item_name = 'variation';
        } else {
            $item_name = rtrim( $export_type, 's' ); // product, order, customer, coupon
        }
        
        $xml = new \SimpleXMLElement( '<?xml version="1.0" encoding="UTF-8"?><' . $root_name . '></' . $root_name . '>' );
        
        foreach ( $rows as $row ) {
            $item = $xml->addChild( $item_name );
            foreach ( $headers as $idx => $header ) {
                $value = $row[ $idx ] ?? '';
                $item->addChild( sanitize_key( $header ), htmlspecialchars( $value ) );
            }
        }
        
        $xml->asXML( $file_path );
    }

    /**
     * Write XLSX file.
     *
     * @param string $file_path File path.
     * @param array  $headers   Headers.
     * @param array  $rows      Rows.
     * @return void
     */
    private static function write_xlsx_file( $file_path, $headers, $rows ) {
        // Try prefixed PhpSpreadsheet first (production), then normal (development)
        $spreadsheet_class = null;
        $writer_class = null;
        
        if ( class_exists( '\PrimeLink\Vendor\PhpOffice\PhpSpreadsheet\Spreadsheet' ) ) {
            $spreadsheet_class = '\PrimeLink\Vendor\PhpOffice\PhpSpreadsheet\Spreadsheet';
            $writer_class = '\PrimeLink\Vendor\PhpOffice\PhpSpreadsheet\Writer\Xlsx';
        } elseif ( class_exists( '\PhpOffice\PhpSpreadsheet\Spreadsheet' ) ) {
            $spreadsheet_class = '\PhpOffice\PhpSpreadsheet\Spreadsheet';
            $writer_class = '\PhpOffice\PhpSpreadsheet\Writer\Xlsx';
        }

        if ( $spreadsheet_class && $writer_class ) {
            try {
                $spreadsheet = new $spreadsheet_class();
                $sheet = $spreadsheet->getActiveSheet();
                
                // Write headers
                $col = 1;
                foreach ( $headers as $header ) {
                    $sheet->setCellValueByColumnAndRow( $col, 1, $header );
                    $col++;
                }
                
                // Write rows
                $row_num = 2;
                foreach ( $rows as $row ) {
                    $col = 1;
                    foreach ( $row as $cell ) {
                        $sheet->setCellValueByColumnAndRow( $col, $row_num, $cell );
                        $col++;
                    }
                    $row_num++;
                }
                
                $writer = new $writer_class( $spreadsheet );
                $writer->save( $file_path );
                return;
            } catch ( \Exception $e ) {
                \PrimeLink\Helpers\LoggerHelper::warning( 'PhpSpreadsheet export failed, falling back to CSV', array(
                    'error' => $e->getMessage(),
                ) );
            }
        }
        
        // Fallback: write as CSV with .xlsx extension (not ideal but works)
        self::write_csv_file( $file_path, $headers, $rows, array( 'csv_delimiter' => ',' ) );
    }

    /**
     * Write PDF file.
     *
     * @param string $file_path  File path.
     * @param array  $headers    Headers.
     * @param array  $rows       Rows.
     * @param array  $format_opts Format options.
     * @return void
     */
    /**
     * Write PDF file.
     *
     * @param string $file_path  File path.
     * @param array  $headers    Headers.
     * @param array  $rows       Rows.
     * @param array  $format_opts Format options.
     * @return bool|WP_Error True on success, WP_Error on failure (with fallback CSV created).
     */
    private static function write_pdf_file( $file_path, $headers, $rows, $format_opts = array() ) {
        // Get PDF options from format_opts
        $pdf_options = array(
            'paper_size'  => $format_opts['pdf_paper_size'] ?? 'A4',
            'orientation' => $format_opts['pdf_orientation'] ?? 'portrait',
            'font'        => $format_opts['pdf_font'] ?? 'DejaVu Sans',
            'margin'      => $format_opts['pdf_margin'] ?? array( 10, 10, 10, 10 ),
        );

        // Get export title
        $title = $format_opts['pdf_title'] ?? __( 'Export Report', 'primelink' );

        // Use PdfHelper to generate PDF
        $result = \PrimeLink\Helpers\PdfHelper::save_table_pdf(
            $file_path,
            $headers,
            $rows,
            $pdf_options,
            $title
        );

        if ( is_wp_error( $result ) ) {
            LoggerHelper::error( 'PDF export failed', array(
                'error' => $result->get_error_message(),
                'file_path' => $file_path,
            ) );
            // Fallback: try to write as CSV
            $csv_path = str_replace( '.pdf', '.csv', $file_path );
            self::write_csv_file( $csv_path, $headers, $rows, array( 'csv_delimiter' => ',' ) );
            // Return error so caller knows PDF failed (but CSV fallback was created)
            return $result;
        }

        return true;
    }

    /**
     * Get export state.
     *
     * @param int $export_id Export ID.
     * @return array State.
     */
    /**
     * Get state option key for export.
     *
     * @param int $export_id Export ID.
     * @return string
     */
    private static function state_key( $export_id ) {
        return 'mt_primelink_export_state_' . absint( $export_id );
    }

    public static function get_state( $export_id ) {
        $state = get_option( self::state_key( $export_id ), array() );
        return wp_parse_args( $state, array(
            'status'    => 'idle',
            'progress'  => 0,
            'current'   => 0,
            'total'     => 0,
            'file_path' => '',
            'file_url'  => '',
            'log'       => array(),
        ) );
    }

    /**
     * Save export state.
     *
     * @param int   $export_id Export ID.
     * @param array $state     State data.
     * @return void
     */
    private static function save_state( $export_id, $state ) {
        update_option( self::state_key( $export_id ), $state, false );
    }

    /**
     * Pause export.
     *
     * @param int $export_id Export ID.
     * @return array State.
     */
    public static function pause( $export_id ) {
        $state = self::get_state( $export_id );
        $state['status'] = 'paused';
        $state['paused'] = true;
        self::save_state( $export_id, $state );
        return $state;
    }

    /**
     * Resume export.
     *
     * @param int $export_id Export ID.
     * @return array State.
     */
    public static function resume( $export_id ) {
        $state = self::get_state( $export_id );
        $state['status'] = 'running';
        $state['paused'] = false;
        self::save_state( $export_id, $state );
        
        // Continue processing if not completed
        if ( $state['status'] !== 'completed' && $state['status'] !== 'error' ) {
            // Get draft and continue processing
            $drafts = get_option( 'mt_primelink_export_drafts', array() );
            if ( ! empty( $drafts[ $export_id ] ) ) {
                $draft = $drafts[ $export_id ];
                // Resume processing from where it left off
                // For now, just mark as running - actual resume logic would need to track position
            }
        }
        
        return $state;
    }

    /**
     * Tick all running exports and start scheduled ones.
     * Used by external cron to process all exports.
     */
    public static function tick_all() {
        $settings = self::get_settings();
        $drafts = get_option( 'mt_primelink_export_drafts', array() );
        if ( empty( $drafts ) || ! is_array( $drafts ) ) {
            return;
        }

        // In external cron mode, proactively start due schedules
        if ( ( $settings['cron_mode'] ?? 'wp' ) === 'external' ) {
            $now = time();
            $updated = false;
            foreach ( $drafts as $export_id => &$draft ) {
                $export_id = absint( $export_id );
                if ( $export_id < 1 ) {
                    continue;
                }
                $schedule = $draft['step_data'][3]['schedule'] ?? array();
                $enabled  = ! empty( $schedule['enabled'] );
                $next_run = isset( $schedule['next_run'] ) ? absint( $schedule['next_run'] ) : 0;
                if ( ! $enabled || $next_run < 1 || $next_run > $now ) {
                    continue;
                }
                $state = get_option( self::state_key( $export_id ), array() );
                if ( isset( $state['status'] ) && $state['status'] === 'running' ) {
                    continue;
                }
                self::start( $export_id, $draft, true );
                $draft['step_data'][3]['schedule'] = self::bump_next_run( $schedule );
                $updated = true;
            }
            unset( $draft );
            if ( $updated ) {
                update_option( 'mt_primelink_export_drafts', $drafts, false );
            }
        }

        // Process all running exports
        foreach ( array_keys( $drafts ) as $export_id ) {
            $export_id = absint( $export_id );
            if ( $export_id < 1 ) {
                continue;
            }
            $state = get_option( self::state_key( $export_id ), array() );
            if ( empty( $state ) || ( $state['status'] ?? '' ) !== 'running' ) {
                continue;
            }
            self::tick( $export_id );
        }
    }

    /**
     * Check and start scheduled exports.
     * Useful as a fallback when WP-Cron is delayed.
     */
    public static function maybe_start_schedules() {
        $settings = self::get_settings();
        $drafts   = get_option( 'mt_primelink_export_drafts', array() );
        if ( empty( $drafts ) || ! is_array( $drafts ) ) {
            return;
        }

        $now     = time();
        $updated = false;

        foreach ( $drafts as $export_id => &$draft ) {
            $export_id = absint( $export_id );
            if ( $export_id < 1 ) {
                continue;
            }
            $schedule = $draft['step_data'][3]['schedule'] ?? array();
            $enabled  = ! empty( $schedule['enabled'] );
            $next_run = isset( $schedule['next_run'] ) ? absint( $schedule['next_run'] ) : 0;
            if ( ! $enabled || $next_run < 1 || $next_run > $now ) {
                continue;
            }
            $state = get_option( self::state_key( $export_id ), array() );
            if ( isset( $state['status'] ) && $state['status'] === 'running' ) {
                continue;
            }
            self::start( $export_id, $draft, true );
            $draft['step_data'][3]['schedule'] = self::bump_next_run( $schedule );
            $updated = true;
        }
        unset( $draft );

        if ( $updated ) {
            update_option( 'mt_primelink_export_drafts', $drafts, false );
        }
    }

    /**
     * Calculate next run time for schedule.
     *
     * @param array $schedule Schedule config.
     * @return array Updated schedule.
     */
    private static function bump_next_run( $schedule ) {
        $interval = $schedule['interval'] ?? 'daily';
        $now      = time();
        // Use current time as base to ensure proper interval spacing
        // This prevents immediate re-runs if the export takes longer than expected
        $current  = $now;
        $map = array(
            'hourly'     => HOUR_IN_SECONDS,
            'twicedaily' => 12 * HOUR_IN_SECONDS,
            'daily'      => DAY_IN_SECONDS,
            'weekly'     => WEEK_IN_SECONDS,
            'monthly'    => 30 * DAY_IN_SECONDS,
        );
        $step = $map[ $interval ] ?? DAY_IN_SECONDS;
        $next = $current + $step;
        $schedule['next_run'] = $next;
        return $schedule;
    }
}

