<?php
if (! defined('ABSPATH')) {
    exit;
} class PufferGo_Product_Import
{
    public const REST_NAMESPACE = 'puffergo/v1';

    public const PAGE_SLUG = 'puffergo-product-import';

    public const BATCH_META_KEY = '_puffergo_import_batch';

    public const BATCHES_OPTION = 'puffergo_product_import_batches';

    public const MAX_ROWS = 500;

    public const MAX_FILE_SIZE = 10485760;

    private $page_hook = false;

    private $schema_cache = null;

    public function __construct()
    {
        add_action('admin_menu', array($this, 'register_page'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
        add_action('admin_footer-edit.php', array($this, 'inject_list_buttons'));
        add_action('admin_post_puffergo_download_product_template', array($this, 'download_template'));
        add_action('admin_post_puffergo_export_products', array($this, 'export_products'));
        add_action('rest_api_init', array($this, 'register_rest_routes'));
    }

    private function column_schema(): array
    {
        if (null !== $this->schema_cache) {
            return $this->schema_cache;
        } $this->schema_cache = array('title' => array('label' => static function () { return __('Product Name', 'puffergo'); }, 'aliases' => array('产品名称', 'Product Title', 'Name'), 'required' => true, 'help' => static function () { return __('The full product name — used as the product page title.', 'puffergo'); }, 'export' => function (WP_Post $post) { return $post->post_title; }, ), 'excerpt' => array('label' => static function () { return __('Short Description', 'puffergo'); }, 'aliases' => array('产品简介'), 'required' => false, 'help' => static function () { return __('A one or two sentence summary shown on the product list.', 'puffergo'); }, 'export' => function (WP_Post $post) { return $post->post_excerpt; }, ), 'content' => array('label' => static function () { return __('Product Details', 'puffergo'); }, 'aliases' => array('产品详情'), 'required' => false, 'help' => static function () { return __('The full product description. Multiple paragraphs are supported.', 'puffergo'); }, 'export' => function (WP_Post $post) {
            $content = str_replace('</p>', "</p>\n\n", $post->post_content);

            return trim(wp_strip_all_tags($content));
        }, ), 'categories' => array('label' => static function () { return __('Categories', 'puffergo'); }, 'aliases' => array('分类'), 'required' => false, 'help' => static function () { return __('Separate multiple categories with a semicolon ";". Categories that do not exist yet are created automatically.', 'puffergo'); }, 'export' => function (WP_Post $post) { return $this->term_names_export($post->ID, PufferGo_Product_CPT::TAX_SLUG); }, ), 'price' => array('label' => static function () { return __('Price', 'puffergo'); }, 'aliases' => array('价格'), 'required' => false, 'help' => static function () { return __('Format: "12.5-18 USD" (range), "45 USD" (fixed), or "Contact for quote". Supported currencies: USD/EUR/CNY/GBP/JPY/HKD, or the symbols $ ¥ € £.', 'puffergo'); }, 'export' => function (WP_Post $post) { return $this->export_price($this->get_meta_json($post->ID, '_puffergo_price')); }, ), 'moq' => array('label' => static function () { return __('MOQ', 'puffergo'); }, 'aliases' => array('起订量MOQ', '起订量', 'MOQ'), 'required' => false, 'help' => static function () { return __('Format: quantity + unit, e.g. "100 pieces" or "50 sets".', 'puffergo'); }, 'export' => function (WP_Post $post) { return $this->export_moq($this->get_meta_json($post->ID, '_puffergo_moq')); }, ), 'lead_time' => array('label' => static function () { return __('Lead Time', 'puffergo'); }, 'aliases' => array('交期'), 'required' => false, 'help' => static function () { return __('Format: "15-20 days" or "10 days" (weeks/months are also supported).', 'puffergo'); }, 'export' => function (WP_Post $post) { return $this->export_lead_time($this->get_meta_json($post->ID, '_puffergo_lead_time')); }, ), 'specs' => array('label' => static function () { return __('Specifications', 'puffergo'); }, 'aliases' => array('规格参数', '规格'), 'required' => false, 'help' => static function () { return __('Format: "name:value; name:value" — separate specs with a semicolon ";", name and value with a colon ":". Use this for anything product-specific: HS code, origin, certifications, SKU, dimensions, materials, etc. — add as many or as few rows as this product needs.', 'puffergo'); }, 'export' => function (WP_Post $post) { return $this->export_specs($this->get_meta_json($post->ID, '_puffergo_specs')); }, ), 'images' => array('label' => static function () { return __('Image URLs', 'puffergo'); }, 'aliases' => array('图片链接', '图片'), 'required' => false, 'help' => static function () { return __('If the images already live somewhere online (an old website, a marketplace listing) paste those URLs here — separate multiple ones with a semicolon ";" and they will be downloaded automatically on import. If there are no URLs yet, leave this blank and upload images on the product\'s edit page after import — imported products start as drafts, so there is time to add images before publishing.', 'puffergo'); }, 'export' => function (WP_Post $post) { return $this->export_images($post->ID); }, ), );

        return $this->schema_cache;
    }

    private function alias_lookup(): array
    {
        $lookup = array();

        foreach ($this->column_schema() as $field => $def) {
            $labels = array($def['label']());
            $labels[] = $field;

            if (! empty($def['aliases'])) {
                $labels = array_merge($labels, $def['aliases']);
            }

foreach ($labels as $label) {
                $key = $this->normalize_header((string) $label);

                if ($key !== '' && ! isset($lookup[ $key ])) {
                    $lookup[ $key ] = $field;
                }
            }
        }

return $lookup;
    }

    private function normalize_header(string $text): string
    {
        return str_replace(array(' ', '　', '*', '＊'), '', trim($text));
    }

    public function register_page(): void
    {
        $this->page_hook = add_submenu_page('edit.php?post_type=' . PufferGo_Product_CPT::CPT_SLUG, __('Import Products', 'puffergo'), __('Import Products', 'puffergo'), 'manage_options', self::PAGE_SLUG, array($this, 'render_page'));
    }

    public function render_page(): void
    { ?>
		<div class="wrap">
			<div id="puffergo-product-import-root"></div>
		</div>
		<?php
    }

    public function enqueue_scripts($hook): void
    {
        if (! $this->page_hook || $hook !== $this->page_hook) {
            return;
        } $plugin_url = plugin_dir_url(dirname(__FILE__));
        $plugin_path = plugin_dir_path(dirname(__FILE__));
        $asset_file = $plugin_path . 'build/admin/product-import/index.asset.php';
        $script_file = $plugin_path . 'build/admin/product-import/index.js';

        if (! file_exists($script_file)) {
            return;
        }

if (file_exists($asset_file)) {
            $asset = include $asset_file;
            $deps = $asset['dependencies'];
            $version = $asset['version'];
        } else {
            $deps = array('wp-element', 'wp-components', 'wp-api-fetch', 'wp-i18n');
            $version = time();
        } wp_enqueue_script('puffergo-product-import', $plugin_url . 'build/admin/product-import/index.js', $deps, $version, true);
        $style_css = $plugin_path . 'build/admin/product-import/style-index.css';

        if (file_exists($style_css)) {
            wp_enqueue_style('puffergo-product-import-styles', $plugin_url . 'build/admin/product-import/style-index.css', array('wp-components'), $version);
        } wp_enqueue_style('wp-components');
        wp_localize_script('puffergo-product-import', 'puffergoProductImport', array('restUrl' => esc_url_raw(rest_url(self::REST_NAMESPACE . '/product-import')), 'nonce' => wp_create_nonce('wp_rest'), 'templateUrl' => $this->template_download_url(), 'productsUrl' => admin_url('edit.php?post_type=' . PufferGo_Product_CPT::CPT_SLUG), 'maxRows' => self::MAX_ROWS, 'maxFileSize' => self::MAX_FILE_SIZE, ));
    }

    private function template_download_url(): string
    {
        return add_query_arg(array('action' => 'puffergo_download_product_template', '_wpnonce' => wp_create_nonce('puffergo_download_product_template'), ), admin_url('admin-post.php'));
    }

    private function export_download_url(): string
    {
        return add_query_arg(array('action' => 'puffergo_export_products', '_wpnonce' => wp_create_nonce('puffergo_export_products'), ), admin_url('admin-post.php'));
    }

    public function inject_list_buttons(): void
    {
        global $typenow;

        if ($typenow !== PufferGo_Product_CPT::CPT_SLUG || ! current_user_can('manage_options')) {
            return;
        } $import_url = esc_url_raw(admin_url('edit.php?post_type=' . PufferGo_Product_CPT::CPT_SLUG . '&page=' . self::PAGE_SLUG));
        $template_url = esc_url_raw($this->template_download_url());
        $export_url = esc_url_raw($this->export_download_url());
        $import_text = esc_html__('Import Products', 'puffergo');
        $tpl_text = esc_html__('Download Template', 'puffergo');
        $export_text = esc_html__('Export Products', 'puffergo'); ?>
		<script>
		( function () {
			var anchor = document.querySelector( '.wrap .page-title-action' );
			if ( ! anchor ) {
				return;
			}
			var importBtn = document.createElement( 'a' );
			importBtn.href = <?php echo wp_json_encode($import_url); ?>;
			importBtn.className = 'page-title-action';
			importBtn.textContent = <?php echo wp_json_encode($import_text); ?>;
			var tplBtn = document.createElement( 'a' );
			tplBtn.href = <?php echo wp_json_encode($template_url); ?>;
			tplBtn.className = 'page-title-action';
			tplBtn.textContent = <?php echo wp_json_encode($tpl_text); ?>;
			var exportBtn = document.createElement( 'a' );
			exportBtn.href = <?php echo wp_json_encode($export_url); ?>;
			exportBtn.className = 'page-title-action';
			exportBtn.textContent = <?php echo wp_json_encode($export_text); ?>;
			anchor.after( importBtn, tplBtn, exportBtn );
		} )();
		</script>
		<?php
    }

    public function download_template(): void
    {
        if (! current_user_can('manage_options')) {
            wp_die(esc_html__('You are not allowed to do that.', 'puffergo'));
        } check_admin_referer('puffergo_download_product_template');
        require_once PUFFERGO_PLUGIN_DIR . '/php/lib/SimpleXLSXGen.php';
        $schema = $this->column_schema();
        $headers = array();

        foreach ($schema as $field => $def) {
            $label = $def['label']();
            $headers[] = $def['required'] ? $label . '*' : $label;
        } $data_rows = array($headers, array('工业级不锈钢球阀 DN50', '316L 不锈钢材质，适用于石油化工、食品制药行业管道控制', '本产品采用整体铸造工艺，球体经精密研磨，密封性能达到 API 598 标准。适用于高温高压工况……', '阀门;球阀', '不锈钢;工业阀门', '12.5-18 USD', '100 pieces', '15-20 天', '材质:316L 不锈钢; 公称压力:PN16; 口径:DN50; 连接方式:法兰; HS编码:8481.80.9990; 产地:浙江省温州市; 认证:CE、ISO 9001、API 598', 'https://example.com/images/ball-valve-1.jpg;https://example.com/images/ball-valve-2.jpg', ), array('气动执行器 AT-52', '双作用铝合金气动执行器，配套球阀蝶阀使用', '', '执行机构', '气动;自动化', '45 USD', '50 sets', '10 天', '扭矩:20N·m; 工作气压:0.5MPa; 本体材质:铝合金; HS编码:8412.39.0000; 产地:浙江省宁波市; 认证:CE', '', ), array('卫生级快装卡箍', '304 不锈钢卫生级卡箍，食品饮料行业专用', '', '管件配件', '', '面议', '1000 pieces', '7-10 天', '规格:25.4mm; 材质:304 不锈钢; 标准:ISO 2852; HS编码:7307.29.0000; 产地:广东省佛山市; 认证:3A、FDA', 'https://example.com/images/clamp-1.jpg', ), );
        $help_rows = array(array(__('Column', 'puffergo'), __('Required', 'puffergo'), __('Instructions', 'puffergo')), );

        foreach ($schema as $field => $def) {
            $label = $def['label']();
            $help_rows[] = array($def['required'] ? $label . '*' : $label, $def['required'] ? __('Required', 'puffergo') : __('Optional', 'puffergo'), $def['help'](), );
        } $help_rows[] = array('', '', '');
        $help_rows[] = array(__('Notes', 'puffergo'), '', __('1. Delete the sample rows before uploading. 2. Do not modify the header row. 3. A single import supports up to 500 products. 4. Imported products start as drafts — review and publish them afterwards.', 'puffergo'), );
        $xlsx = Shuchkin\SimpleXLSXGen::fromArray($data_rows, __('Products', 'puffergo'));
        $xlsx->addSheet($help_rows, __('Instructions', 'puffergo'));
        $xlsx->downloadAs('puffergo-products-template.xlsx');
        exit;
    }

    public function export_products(): void
    {
        if (! current_user_can('manage_options')) {
            wp_die(esc_html__('You are not allowed to do that.', 'puffergo'));
        } check_admin_referer('puffergo_export_products');

        if (function_exists('set_time_limit')) {
            @set_time_limit(600);
        } require_once PUFFERGO_PLUGIN_DIR . '/php/lib/SimpleXLSXGen.php';
        $schema = $this->column_schema();
        $headers = array();

        foreach ($schema as $field => $def) {
            $label = $def['label']();
            $headers[] = $def['required'] ? $label . '*' : $label;
        } $rows = array($headers);
        $paged = 1;
        do {
            $query = new WP_Query(array('post_type' => PufferGo_Product_CPT::CPT_SLUG, 'post_status' => array('publish', 'draft'), 'orderby' => 'date', 'order' => 'DESC', 'posts_per_page' => 100, 'paged' => $paged, 'no_found_rows' => true, ));

            foreach ($query->posts as $post) {
                $row = array();

                foreach ($schema as $field => $def) {
                    $row[] = (string) $def['export']($post);
                } $rows[] = $row;
            } $has_more = count($query->posts) === 100;
            $paged++;
            wp_reset_postdata();
        } while ($has_more);
        $filename = 'puffergo-products-export-' . gmdate('Ymd') . '.xlsx';
        $xlsx = Shuchkin\SimpleXLSXGen::fromArray($rows, __('Products', 'puffergo'));
        $xlsx->downloadAs($filename);
        exit;
    }

    private function get_meta_json(int $post_id, string $meta_key): array
    {
        $raw = get_post_meta($post_id, $meta_key, true);

        if (is_array($raw)) {
            return $raw;
        }

if (! is_string($raw) || $raw === '') {
            return array();
        } $decoded = json_decode($raw, true);

        return is_array($decoded) ? $decoded : array();
    }

    private function term_names_export(int $post_id, string $taxonomy): string
    {
        $terms = get_the_terms($post_id, $taxonomy);

        if (empty($terms) || is_wp_error($terms)) {
            return '';
        }

return implode(';', wp_list_pluck($terms, 'name'));
    }

    private function export_specs(array $specs): string
    {
        $parts = array();

        foreach ($specs as $spec) {
            if (is_array($spec) && isset($spec['key']) && (string) $spec['key'] !== '') {
                $parts[] = $spec['key'] . ':' . ($spec['value'] ?? '');
            }
        }

return implode('; ', $parts);
    }

    private function export_images(int $post_id): string
    {
        $gallery = PufferGo_Product_CPT::normalize_gallery(get_post_meta($post_id, '_puffergo_gallery', true));
        $urls = array();

        foreach ($gallery as $item) {
            if (! is_array($item)) {
                continue;
            }

if (($item['type'] ?? '') === 'url' && ! empty($item['src'])) {
                $urls[] = $item['src'];
            } elseif (! empty($item['id'])) {
                $url = wp_get_attachment_url((int) $item['id']);

                if ($url) {
                    $urls[] = $url;
                }
            }
        }

return implode(';', $urls);
    }

    public function register_rest_routes(): void
    {
        register_rest_route(self::REST_NAMESPACE, '/product-import/parse', array('methods' => 'POST', 'callback' => array($this, 'rest_parse'), 'permission_callback' => array($this, 'can_manage'), ));
        register_rest_route(self::REST_NAMESPACE, '/product-import/import', array('methods' => 'POST', 'callback' => array($this, 'rest_import'), 'permission_callback' => array($this, 'can_manage'), ));
        register_rest_route(self::REST_NAMESPACE, '/product-import/undo', array('methods' => 'POST', 'callback' => array($this, 'rest_undo'), 'permission_callback' => array($this, 'can_manage'), ));
        register_rest_route(self::REST_NAMESPACE, '/product-import/batches', array('methods' => 'GET', 'callback' => array($this, 'rest_batches'), 'permission_callback' => array($this, 'can_manage'), ));
    }

    public function can_manage(): bool
    {
        return current_user_can('manage_options');
    }

    public function rest_parse(WP_REST_Request $request)
    {
        $files = $request->get_file_params();

        if (empty($files['file']) || ! empty($files['file']['error'])) {
            return new WP_Error('no_file', __('No file uploaded.', 'puffergo'), array('status' => 400));
        } $file = $files['file'];

        if ($file['size'] > self::MAX_FILE_SIZE) {
            return new WP_Error('file_too_large', __('File exceeds the 10MB limit.', 'puffergo'), array('status' => 400));
        } $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));

        if (! in_array($ext, array('xlsx', 'csv'), true)) {
            return new WP_Error('bad_type', __('Only .xlsx and .csv files are supported.', 'puffergo'), array('status' => 400));
        } $grid = ($ext === 'xlsx') ? $this->read_xlsx($file['tmp_name']) : $this->read_csv($file['tmp_name']);

        if (is_wp_error($grid)) {
            return $grid;
        }

if (count($grid) < 2) {
            return new WP_Error('empty_file', __('The file has no data rows.', 'puffergo'), array('status' => 400));
        } $col_map = $this->map_headers($grid[0]);

        if (! isset($col_map['title'])) {
            return new WP_Error('missing_title_column', __('Could not find the required "Product Name" column. Please use the provided template.', 'puffergo'), array('status' => 400));
        } $data_rows = array_slice($grid, 1, self::MAX_ROWS + 1);

        if (count($data_rows) > self::MAX_ROWS) {
            return new WP_Error('too_many_rows', sprintf(__('Too many rows — a single import supports up to %d products. Please split the file.', 'puffergo'), self::MAX_ROWS), array('status' => 400));
        } $rows = array();
        $valid = 0;
        $invalid = 0;
        $duplicates = 0;

        foreach ($data_rows as $i => $cells) {
            if ($this->is_empty_row($cells)) {
                continue;
            } $row = $this->parse_row($cells, $col_map, $i + 2);
            $rows[] = $row;
            $row['valid'] ? $valid++ : $invalid++;

            if ($row['duplicate']) {
                $duplicates++;
            }
        }

if (empty($rows)) {
            return new WP_Error('empty_file', __('The file has no data rows.', 'puffergo'), array('status' => 400));
        }

return new WP_REST_Response(array('rows' => $rows, 'summary' => array('total' => count($rows), 'valid' => $valid, 'invalid' => $invalid, 'duplicates' => $duplicates, ), ));
    }

    private function read_xlsx(string $path)
    {
        require_once PUFFERGO_PLUGIN_DIR . '/php/lib/SimpleXLSX.php';
        $xlsx = Shuchkin\SimpleXLSX::parse($path);

        if (! $xlsx) {
            return new WP_Error('parse_failed', __('Could not read the Excel file. Please re-save it as .xlsx and try again.', 'puffergo'), array('status' => 400));
        }

return $xlsx->rows(0);
    }

    private function read_csv(string $path)
    {
        $raw = file_get_contents($path);

        if ($raw === false || $raw === '') {
            return new WP_Error('parse_failed', __('Could not read the CSV file.', 'puffergo'), array('status' => 400));
        }

if (strncmp($raw, "\xEF\xBB\xBF", 3) === 0) {
            $raw = substr($raw, 3);
        } else {
            $encoding = mb_detect_encoding($raw, array('UTF-8', 'GB18030'), true);

            if ($encoding && $encoding !== 'UTF-8') {
                $raw = mb_convert_encoding($raw, 'UTF-8', $encoding);
            }
        } $stream = fopen('php://temp', 'r+');
        fwrite($stream, $raw);
        rewind($stream);
        $grid = array();

        while (($cells = fgetcsv($stream)) !== false) {
            if ($cells === array(null)) {
                continue;
            } $grid[] = $cells;
        } fclose($stream);

        return $grid;
    }

    private function map_headers(array $header_cells): array
    {
        $lookup = $this->alias_lookup();
        $map = array();

        foreach ($header_cells as $index => $cell) {
            $name = $this->normalize_header((string) $cell);

            if (isset($lookup[ $name ]) && ! isset($map[ $lookup[ $name ] ])) {
                $map[ $lookup[ $name ] ] = $index;
            }
        }

return $map;
    }

    private function is_empty_row(array $cells): bool
    {
        foreach ($cells as $cell) {
            if (trim((string) $cell) !== '') {
                return false;
            }
        }

return true;
    }

    private function split_list(string $value): array
    {
        $parts = preg_split('/[;；\n]+/u', $value);

        return array_values(array_filter(array_map('trim', $parts), 'strlen'));
    }

    private function cell(array $cells, array $col_map, string $field): string
    {
        if (! isset($col_map[ $field ]) || ! isset($cells[ $col_map[ $field ] ])) {
            return '';
        }

return trim((string) $cells[ $col_map[ $field ] ]);
    }

    private function parse_row(array $cells, array $col_map, int $row_number): array
    {
        $errors = array();
        $title = $this->cell($cells, $col_map, 'title');

        if ($title === '') {
            $errors[] = __('Product name is required.', 'puffergo');
        } $images = array();

        foreach ($this->split_list($this->cell($cells, $col_map, 'images')) as $url) {
            $clean = esc_url_raw($url);

            if ($clean && preg_match('#^https?://#i', $clean)) {
                $images[] = $clean;
            } else {
                $errors[] = sprintf(__('Invalid image URL skipped: %s', 'puffergo'), $url);
            }
        } $specs = array();

        foreach ($this->split_list($this->cell($cells, $col_map, 'specs')) as $pair) {
            $kv = preg_split('/[:：]/u', $pair, 2);

            if (count($kv) === 2 && trim($kv[0]) !== '') {
                $specs[] = array('key' => trim($kv[0]), 'value' => trim($kv[1]));
            } else {
                $errors[] = sprintf(__('Unrecognized spec skipped (expected 名称:值): %s', 'puffergo'), $pair);
            }
        } $price = $this->parse_price($this->cell($cells, $col_map, 'price'), $errors);
        $moq = $this->parse_moq($this->cell($cells, $col_map, 'moq'), $errors);
        $lead_time = $this->parse_lead_time($this->cell($cells, $col_map, 'lead_time'), $errors);
        $duplicate = $title !== '' && $this->product_exists($title);

        return array('row_number' => $row_number, 'title' => $title, 'excerpt' => $this->cell($cells, $col_map, 'excerpt'), 'content' => $this->cell($cells, $col_map, 'content'), 'categories' => $this->split_list($this->cell($cells, $col_map, 'categories')), 'price' => $price, 'moq' => $moq, 'lead_time' => $lead_time, 'specs' => $specs, 'images' => $images, 'valid' => $title !== '', 'errors' => $errors, 'duplicate' => $duplicate, );
    }

    private function parse_price(string $text, array &$errors): array
    {
        if ($text === '') {
            return array();
        }

if (preg_match('/面议|询价|电议|contact|negotiable/iu', $text)) {
            return array('type' => 'contact');
        } $currency = 'USD';

        if (preg_match('/\b(USD|EUR|CNY|GBP|JPY|HKD)\b/i', $text, $m)) {
            $currency = strtoupper($m[1]);
        } elseif (strpos($text, '€') !== false) {
            $currency = 'EUR';
        } elseif (strpos($text, '£') !== false) {
            $currency = 'GBP';
        } elseif (strpos($text, '¥') !== false || strpos($text, '￥') !== false || preg_match('/元|人民币|RMB/u', $text)) {
            $currency = 'CNY';
        } $range = $this->parse_number_range($text);

        if ($range === null) {
            $errors[] = sprintf(__('Could not read price "%s" — expected e.g. 12.5-18 USD, 45 USD or 面议; the field was left empty.', 'puffergo'), $text);

            return array();
        }

if (isset($range['max'])) {
            return array('min' => $range['min'], 'max' => $range['max'], 'unit' => $currency);
        }

return array('value' => $range['min'], 'unit' => $currency);
    }

    private function export_price(array $data): string
    {
        if (empty($data)) {
            return '';
        }

if (($data['type'] ?? '') === 'contact') {
            return __('Contact for quote', 'puffergo');
        } $unit = $data['unit'] ?? 'USD';

        if (isset($data['min']) && isset($data['max'])) {
            return $this->format_number($data['min']) . '-' . $this->format_number($data['max']) . ' ' . $unit;
        }

if (isset($data['value'])) {
            return $this->format_number($data['value']) . ' ' . $unit;
        }

return '';
    }

    private function parse_moq(string $text, array &$errors): array
    {
        if ($text === '') {
            return array();
        }

if (! preg_match('/([0-9][0-9,]*(?:\.[0-9]+)?)/', $text, $m)) {
            $errors[] = sprintf(__('Could not read MOQ "%s" — expected e.g. 100 pieces; the field was left empty.', 'puffergo'), $text);

            return array();
        } $value = (float) str_replace(',', '', $m[1]);

        return array('value' => $value, 'unit' => $this->match_unit($text, 'quantity', 'pieces'));
    }

    private function export_moq(array $data): string
    {
        if (empty($data) || ! isset($data['value'])) {
            return '';
        } $unit = $data['unit'] ?? 'pieces';

        return $this->format_number($data['value']) . ' ' . $unit;
    }

    private function parse_lead_time(string $text, array &$errors): array
    {
        if ($text === '') {
            return array();
        } $range = $this->parse_number_range($text);

        if ($range === null) {
            $errors[] = sprintf(__('Could not read lead time "%s" — expected e.g. 15-20 天; the field was left empty.', 'puffergo'), $text);

            return array();
        } $unit = $this->match_unit($text, 'time', 'days');

        if (isset($range['max'])) {
            return array('min' => $range['min'], 'max' => $range['max'], 'unit' => $unit);
        }

return array('value' => $range['min'], 'unit' => $unit);
    }

    private function export_lead_time(array $data): string
    {
        if (empty($data)) {
            return '';
        } $unit = $data['unit'] ?? 'days';

        if (isset($data['min']) && isset($data['max'])) {
            return $this->format_number($data['min']) . '-' . $this->format_number($data['max']) . ' ' . $unit;
        }

if (isset($data['value'])) {
            return $this->format_number($data['value']) . ' ' . $unit;
        }

return '';
    }

    private function format_number($value): string
    {
        $value = (float) $value;

        if (floor($value) === $value) {
            return (string) (int) $value;
        }

return rtrim(rtrim(number_format($value, 6, '.', ''), '0'), '.');
    }

    private function parse_number_range(string $text): ?array
    {
        $num = '([0-9][0-9,]*(?:\.[0-9]+)?)';

        if (preg_match('/' . $num . '\s*[-–~～至]\s*' . $num . '/u', $text, $m)) {
            return array('min' => (float) str_replace(',', '', $m[1]), 'max' => (float) str_replace(',', '', $m[2]), );
        }

if (preg_match('/' . $num . '/', $text, $m)) {
            return array('min' => (float) str_replace(',', '', $m[1]));
        }

return null;
    }

    private function match_unit(string $text, string $unit_type, string $default): string
    {
        $config = PufferGo_Unit_Config::get_instance();

        foreach ($config->get_units($unit_type) as $code => $unit_config) {
            $needles = array($code);
            $needles[] = $unit_config['labels']['en_US'] ?? '';
            $needles[] = $unit_config['labels']['zh_CN'] ?? '';

            foreach (array_filter($needles) as $needle) {
                if (mb_stripos($text, $needle) !== false) {
                    return $code;
                }
            }
        }

return $default;
    }

    private function product_exists(string $title): bool
    {
        return $this->find_product_by_title($title) > 0;
    }

    private function find_product_by_title(string $title): int
    {
        global $wpdb;
        $id = $wpdb->get_var($wpdb->prepare("SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_title = %s AND post_status NOT IN ('trash','auto-draft') LIMIT 1", PufferGo_Product_CPT::CPT_SLUG, $title));

        return (int) $id;
    }

    public function rest_import(WP_REST_Request $request)
    {
        $rows = $request->get_param('rows');

        if (! is_array($rows) || empty($rows)) {
            return new WP_Error('no_rows', __('No rows to import.', 'puffergo'), array('status' => 400));
        }

if (count($rows) > self::MAX_ROWS) {
            return new WP_Error('too_many_rows', __('Too many rows.', 'puffergo'), array('status' => 400));
        } $overwrite = (bool) $request->get_param('overwrite_duplicates');

        if (function_exists('set_time_limit')) {
            @set_time_limit(600);
        } PufferGo_Content_Importer::reset_caches();
        $batch_id = uniqid('pgi_');
        $marker = array(self::BATCH_META_KEY => $batch_id);
        $created = array();
        $skipped = 0;
        $failed = array();
        $image_failures = array();

        foreach ($rows as $row) {
            $row_number = isset($row['row_number']) ? (int) $row['row_number'] : 0;
            $title = isset($row['title']) ? sanitize_text_field((string) $row['title']) : '';

            if ($title === '') {
                $failed[] = array('row_number' => $row_number, 'reason' => __('Product name is required.', 'puffergo'));
                continue;
            } $existing_id = $this->find_product_by_title($title);

            if ($existing_id) {
                if (! $overwrite) {
                    $skipped++;
                    continue;
                } wp_delete_post($existing_id, true);
            } $data = $this->row_to_post_data($row);
            $post_id = PufferGo_Content_Importer::create_post(PufferGo_Product_CPT::CPT_SLUG, $data, array('post_status' => 'draft', 'marker_meta' => $marker, ));

            if (! $post_id) {
                $failed[] = array('row_number' => $row_number, 'reason' => __('Failed to create the post.', 'puffergo'));
                continue;
            } $created[] = $post_id;
            $images = isset($row['images']) && is_array($row['images']) ? array_filter(array_map('esc_url_raw', $row['images'])) : array();

            if (! empty($images)) {
                $image_ids = PufferGo_Content_Importer::sideload_images($post_id, array_values($images), array('marker_meta' => $marker));

                foreach (array_values($images) as $idx => $url) {
                    if (empty($image_ids[ $idx ])) {
                        $image_failures[] = $url;
                    }
                } PufferGo_Content_Importer::apply_images_to_post(PufferGo_Product_CPT::CPT_SLUG, $post_id, $image_ids);
            }
        }

if (! empty($created)) {
            $batches = get_option(self::BATCHES_OPTION, array());
            $batches[] = array('batch_id' => $batch_id, 'imported_at' => current_time('mysql'), 'count' => count($created), 'post_ids' => $created, );
            update_option(self::BATCHES_OPTION, $batches, false);
        }

return new WP_REST_Response(array('batch_id' => $batch_id, 'created' => $created, 'skipped' => $skipped, 'failed' => $failed, 'image_failures' => array_values(array_unique($image_failures)), ));
    }

    private function row_to_post_data(array $row): array
    {
        $sanitize_list = function ($value): array {
            if (! is_array($value)) {
                return array();
            }

return array_values(array_filter(array_map('sanitize_text_field', array_map('strval', $value)), 'strlen'));
        };
        $meta = array();

        foreach (array('price', 'moq', 'lead_time') as $field) {
            if (! empty($row[ $field ]) && is_array($row[ $field ])) {
                $meta[ '_puffergo_' . $field ] = PufferGo_Unit_Formatter::sanitize($row[ $field ], $field === 'price' ? 'currency' : ($field === 'moq' ? 'quantity' : 'time'));
            }
        } $specs = array();

        if (! empty($row['specs']) && is_array($row['specs'])) {
            foreach ($row['specs'] as $spec) {
                if (is_array($spec) && isset($spec['key']) && trim((string) $spec['key']) !== '') {
                    $specs[] = array('key' => sanitize_text_field((string) $spec['key']), 'value' => sanitize_text_field((string) ($spec['value'] ?? '')), );
                }
            }
        }

if (! empty($specs)) {
            $meta['_puffergo_specs'] = $specs;
        } $taxonomies = array();
        $categories = $sanitize_list($row['categories'] ?? array());

        if (! empty($categories)) {
            $taxonomies[ PufferGo_Product_CPT::TAX_SLUG ] = $categories;
            PufferGo_Content_Importer::ensure_terms(array(PufferGo_Product_CPT::TAX_SLUG => $categories));
        } $content = isset($row['content']) ? wp_kses_post((string) $row['content']) : '';

        if ($content !== '' && strpos($content, '<') === false) {
            $content = wpautop($content);
        }

return array('title' => sanitize_text_field((string) $row['title']), 'excerpt' => sanitize_text_field((string) ($row['excerpt'] ?? '')), 'content' => $content, 'taxonomies' => $taxonomies, 'meta' => $meta, );
    }

    public function rest_undo(WP_REST_Request $request)
    {
        $batch_id = sanitize_text_field((string) $request->get_param('batch_id'));

        if ($batch_id === '') {
            return new WP_Error('no_batch', __('Missing batch id.', 'puffergo'), array('status' => 400));
        } $posts = get_posts(array('post_type' => PufferGo_Product_CPT::CPT_SLUG, 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, 'meta_query' => array(array('key' => self::BATCH_META_KEY, 'value' => $batch_id, )), ));
        $deleted = 0;

        foreach ($posts as $post_id) {
            PufferGo_Content_Importer::delete_marked_attachments($post_id, self::BATCH_META_KEY, $batch_id);
            wp_delete_post($post_id, true);
            $deleted++;
        } $batches = get_option(self::BATCHES_OPTION, array());
        $batches = array_values(array_filter($batches, function ($batch) use ($batch_id) { return ($batch['batch_id'] ?? '') !== $batch_id; }));
        update_option(self::BATCHES_OPTION, $batches, false);

        return new WP_REST_Response(array('success' => true, 'deleted' => $deleted, ));
    }

    public function rest_batches()
    {
        $batches = get_option(self::BATCHES_OPTION, array());
        $out = array();

        foreach ($batches as $batch) {
            $out[] = array('batch_id' => $batch['batch_id'] ?? '', 'imported_at' => $batch['imported_at'] ?? '', 'count' => (int) ($batch['count'] ?? 0), );
        }

return new WP_REST_Response(array('batches' => $out));
    }
}
