<?php
/**
 * @package Bizmart - Cost of Goods, Analytics, Redesigned Order and Product page
 * @license GPL-2.0-or-later
 *
 * Plugin Name: Bizmart - Cost of Goods, Analytics, Redesigned Order and Product page
 * Description: Easily add Cost of Goods (purchase price/cost tracking) to WooCommerce. Includes an analytics dashboard to track profit margins, a simplified product creation interface, and a modern, fast order panel that are cleaner and easier to use than default WooCommerce.
 * Version:     1.0.2
 * Author:      Fethallah Bemmami
 * Author URI:  https://bizmart.software
 * Text Domain: bizmart-cost-of-goods
 * Requires at least: 5.8
 * Tested up to: 7.0
 * Requires PHP: 7.4
 * License:     GPL-2.0-or-later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 */

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

define('BIZMART_CORE_VERSION', '1.0.2');

/* ─── Freemius SDK Integration ───────────────────────────────────────── */
if ( ! function_exists( 'bizmart_fs' ) ) {
    // Create a helper function for easy SDK access.
    function bizmart_fs() {
        global $bizmart_fs;

        if ( ! isset( $bizmart_fs ) ) {
            // Include Freemius SDK.
            $freemius_start = dirname( __FILE__ ) . '/vendor/freemius/start.php';
            if ( ! file_exists( $freemius_start ) ) {
                return null;
            }

            require_once $freemius_start;

            $bizmart_fs = fs_dynamic_init( array(
                'id'                  => '24317',
                'slug'                => 'bizmart',
                'type'                => 'plugin',
                'public_key'          => 'pk_cd1384eace8f57a7042f5a7fdf95b',
                'is_premium'          => false,
                'has_addons'          => true,
                'has_paid_plans'      => false,
                'is_org_compliant'    => true,
                'menu'                => array(
                    'slug'           => 'bizmart',
                    'first-path'     => 'admin.php?page=bizmart',
                    'account'        => true,
                    'contact'        => false,
                    'support'        => false,
                ),
            ) );
        }

        return $bizmart_fs;
    }

    // Init Freemius only when the bundled SDK is available.
    if ( file_exists( dirname( __FILE__ ) . '/vendor/freemius/start.php' ) ) {
        bizmart_fs();
        if ( is_object( bizmart_fs() ) ) {
            bizmart_fs()->add_action( 'after_uninstall', 'bizmart_fs_uninstall_cleanup' );
        }
        // Signal that SDK was initiated.
        do_action( 'bizmart_fs_loaded' );
    } else {
        add_action( 'admin_notices', function() {
            if ( ! current_user_can( 'activate_plugins' ) ) {
                return;
            }

            echo '<div class="notice notice-error"><p><strong>Bizmart - Cost of Goods, Analytics, Redesigned Order and Product page</strong> is missing its bundled Freemius SDK files. Reinstall the plugin package from a complete archive.</p></div>';
        } );
    }
}

if (!function_exists('bizmart_fs_uninstall_cleanup')) {
    function bizmart_fs_uninstall_cleanup(): void {
        $options_to_delete = [
            'bizmart_settings',
            'bizmart_db_version',
            'bizmart_core_version',
            'bizmart_core_installed_at',
            'bizmart_final_import_job',
            'bizmart_final_cleanup_job',
            'biz_admin_language',
            'biz_company_name',
            'biz_company_phone',
            'biz_company_address',
            'biz_company_logo',
            'biz_currency',
            'biz_import_presets',
            'biz_expense_categories',
        ];

        foreach ($options_to_delete as $opt) {
            delete_option($opt);
        }

        $blog_id = (int) get_current_blog_id();

        $transients_to_delete = [
            'bizmart_inventory_totals_v7_' . $blog_id,
            'bizmart_inventory_totals_v6_' . $blog_id,
            'bizmart_dash_missing_purchase_prices_v2',
            'bizmart_flush_rewrite',
        ];

        foreach ($transients_to_delete as $transient) {
            delete_transient($transient);
        }

        wp_clear_scheduled_hook('bizmart_final_import_job_process');
        wp_clear_scheduled_hook('bizmart_final_cleanup_job_process');

        if (defined('BIZMART_REMOVE_ALL_DATA') && BIZMART_REMOVE_ALL_DATA === true) {
            global $wpdb;

            $tables = [
                $wpdb->prefix . 'bizmart_products',
                $wpdb->prefix . 'bizmart_expenses',
                $wpdb->prefix . 'bizmart_suppliers',
                $wpdb->prefix . 'bizmart_invoices',
                $wpdb->prefix . 'bizmart_invoice_items',
                $wpdb->prefix . 'bizmart_payments',
                $wpdb->prefix . 'bizmart_price_history',
                $wpdb->prefix . 'bizmart_supplier_history',
            ];

            foreach ($tables as $table) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                $wpdb->query("DROP TABLE IF EXISTS `{$table}`");
            }
        }
    }
}

/* ─── Add-Ons menu highlight (green + bold to catch attention) ───────── */
add_action('admin_head', function() {
    echo '<style>
        #adminmenu .toplevel_page_bizmart a[href*="bizmart-addons"] {
            color: #46b450 !important;
            font-weight: 600;
        }
        #adminmenu .toplevel_page_bizmart a[href*="bizmart-addons"]:hover {
            color: #7ad03a !important;
        }
    </style>';
});

/* ─── Constants (unconditional — Core owns these) ────────────────────── */
define('BIZMART_CORE_DIR',     plugin_dir_path(__FILE__));
define('BIZMART_CORE_URL',     plugin_dir_url(__FILE__));
if (!defined('BIZMART_CORE_VERSION')) {
    if (!defined('ABSPATH')) exit;

define('BIZMART_CORE_VERSION', '2.2.0');
}
define('BIZMART_CORE_FILE',    __FILE__);
define('BIZMART_CORE_LOADED',  true);

// Legacy alias — some older code references BIZMART_PLUGIN_FILE
if (!defined('BIZMART_PLUGIN_FILE')) {
    define('BIZMART_PLUGIN_FILE', __FILE__);
}

/* ─── Shared helpers (single source of truth) ────────────────────────── */
(static function() {
    foreach ( array(
        'includes/shared.php',
        'includes/cache.php',
        'includes/analytics-sync.php',
        'includes/metrics.php',
        'includes/wac.php',
        'includes/price-history.php',
        'includes/expense-analytics.php',
        'includes/demo-data-admin.php',
        'includes/barcodes.php',
    ) as $relative_file ) {
        $absolute_file = BIZMART_CORE_DIR . $relative_file;
        if ( file_exists( $absolute_file ) ) {
            require_once $absolute_file;
        }
    }

    // Lazy-load i18n-ar.php only when Arabic admin language is active
    if (get_option('biz_admin_language', 'en') === 'ar') {
        $i18n_ar_file = BIZMART_CORE_DIR . 'includes/i18n-ar.php';
        if ( file_exists( $i18n_ar_file ) ) {
            require_once $i18n_ar_file;
        }
    }

    // Lazy-load importer on admin AJAX requests only (not on every page load)
    if (is_admin()) {
        $importer_file = BIZMART_CORE_DIR . 'includes/importer.php';
        if ( file_exists( $importer_file ) ) {
            require_once $importer_file;
        }
    }

    // Orders & Products AJAX handlers (Core owns these)
    foreach ( array( 'includes/orders-ajax.php', 'includes/products-ajax.php' ) as $relative_file ) {
        $absolute_file = BIZMART_CORE_DIR . $relative_file;
        if ( file_exists( $absolute_file ) ) {
            require_once $absolute_file;
        }
    }
})();

/* ─── Public API for Addons ──────────────────────────────────────────── */

/**
 * Get core version string.
 */
if (!function_exists('bizmart_core_version')) {
    function bizmart_core_version(): string {
        return BIZMART_CORE_VERSION;
    }
}

/**
 * Get core paths/URLs.
 */
if (!function_exists('bizmart_core_paths')) {
    function bizmart_core_paths(): array {
        return [
            'dir'     => BIZMART_CORE_DIR,
            'url'     => BIZMART_CORE_URL,
            'file'    => BIZMART_CORE_FILE,
            'version' => BIZMART_CORE_VERSION,
        ];
    }
}

/**
 * Check if WooCommerce is active.
 */
if (!function_exists('bizmart_wc_active')) {
    function bizmart_wc_active(): bool {
        return class_exists('WooCommerce') || function_exists('WC');
    }
}

/* Translations: WordPress will automatically load plugin translations when hosted on wordpress.org. */

/* ─── WooCommerce dependency notice ──────────────────────────────────── */
add_action('admin_notices', function() {
    if (bizmart_wc_active()) { return; }
    if (!current_user_can('activate_plugins')) { return; }
    $screen = function_exists('get_current_screen') ? get_current_screen() : null;
    if ($screen && strpos((string)$screen->id, 'bizmart') !== false) {
        echo '<div class="notice notice-warning"><p><strong>WooCommerce is required</strong> for Bizmart to work correctly. Please install/activate WooCommerce.</p></div>';
    }
});

/**
 * Include an admin page, or show a friendly notice if WooCommerce is missing.
 */
if (!function_exists('bizmart_core_include_admin_or_wc_notice')) {
    function bizmart_core_include_admin_or_wc_notice(string $relative_path): void {
        if (!bizmart_wc_active()) {
            echo '<div class="wrap"><h1>Bizmart</h1><div class="notice notice-warning"><p><strong>WooCommerce is required</strong> for Bizmart to work correctly. Please install/activate WooCommerce.</p></div></div>';
            return;
        }
        include BIZMART_CORE_DIR . ltrim($relative_path, '/');
    }
}

/* ─── Core admin menu (Dashboard + Settings) ─────────────────────────── */
add_action('admin_menu', function() {
    add_menu_page(
        'Bizmart',
        'Bizmart',
        'manage_woocommerce',
        'bizmart',
        function() { bizmart_core_include_admin_or_wc_notice('admin/dashboard.php'); },
        'dashicons-chart-bar',
        30
    );
}, 9);

// Add global class to admin body on Bizmart pages
add_filter('admin_body_class', function($classes) {
    $screen = get_current_screen();
    if ($screen && strpos($screen->id, 'bizmart') !== false) {
        $classes .= ' bizmart-page';
    }
    return $classes;
});

/* ─── Rename first submenu "Bizmart" → "Dashboard" (runs after ALL submenus registered) ─── */
add_action('admin_menu', function() {
    global $submenu;
    if (isset($submenu['bizmart'])) {
        $submenu['bizmart'][0][0] = bizmart_text('Dashboard', 'Tableau de bord', 'لوحة التحكم');
    }
}, 999);

/* ─── Orders submenu (Core owns) ─────────────────────────────────────── */
add_action('admin_menu', function() {
    // Orders — with notification badge for pending/failed/on-hold
    $order_badge = '';
    if (function_exists('wc_get_orders')) {
        $pending_count = 0;
        foreach (['pending', 'on-hold', 'failed'] as $st) {
            $pending_count += count(wc_get_orders(['status' => $st, 'limit' => -1, 'return' => 'ids']));
        }
        if ($pending_count > 0) {
            $order_badge = ' <span class="awaiting-mod update-plugins count-' . $pending_count . '"><span class="plugin-count">' . $pending_count . '</span></span>';
        }
    }
    add_submenu_page(
        'bizmart',
        bizmart_text('Orders', 'Commandes', 'الطلبات'),
        bizmart_text('Orders', 'Commandes', 'الطلبات') . $order_badge,
        'manage_woocommerce',
        'bizmart-orders',
        function() { bizmart_core_include_admin_or_wc_notice('admin/orders.php'); }
    );
}, 11);

/* ─── Products submenu (Core owns) ───────────────────────────────────── */
add_action('admin_menu', function() {
    add_submenu_page(
        'bizmart',
        bizmart_text('Products', 'Produits', 'المنتجات'),
        bizmart_text('Products', 'Produits', 'المنتجات'),
        'manage_woocommerce',
        'bizmart-products',
        function() { bizmart_core_include_admin_or_wc_notice('admin/products.php'); }
    );
}, 12);

/* ─── Settings submenu — registered at priority 99 to appear last ───── */
add_action('admin_menu', function() {
    add_submenu_page(
        'bizmart',
        bizmart_text('Settings', 'Paramètres', 'الإعدادات'),
        bizmart_text('Settings', 'Paramètres', 'الإعدادات'),
        'manage_woocommerce',
        'bizmart-settings',
        function() { bizmart_core_include_admin_or_wc_notice('admin/settings.php'); }
    );
}, 99);

// Submenu label rename moved to priority 999 above to ensure it runs after all addon submenus are registered.

/* ─── Admin assets (base handles — addon must NOT re-register these) ── */
if (!function_exists('bizmart_enqueue_base_admin_assets')) {
    function bizmart_enqueue_base_admin_assets(string $page = ''): void {
        // Prevent double-enqueue if already done this request
        if (did_action('bizmart_base_assets_enqueued')) { return; }

        // jQuery in header (inline scripts depend on it)
        wp_enqueue_script('jquery');
        if (function_exists('wp_script_add_data')) {
            wp_script_add_data('jquery', 'group', 0);
            wp_script_add_data('jquery-core', 'group', 0);
            wp_script_add_data('jquery-migrate', 'group', 0);
        }

        wp_enqueue_style('bizmart-admin-css', BIZMART_CORE_URL . 'assets/admin.css', [], BIZMART_CORE_VERSION);
        wp_enqueue_script('bizmart-admin-js', BIZMART_CORE_URL . 'assets/admin.js', ['jquery'], BIZMART_CORE_VERSION, true);

        // Settings-only phone assets
        if (strpos($page, 'bizmart-settings') !== false) {
            wp_enqueue_style('intl-tel-input', BIZMART_CORE_URL . 'assets/vendor/intlTelInput.min.css', [], '21.0.0');
            wp_enqueue_script('intl-tel-input', BIZMART_CORE_URL . 'assets/vendor/intlTelInput.min.js', ['jquery'], '21.0.0', true);
            wp_enqueue_script('bizmart-phone-js', BIZMART_CORE_URL . 'assets/admin-phone.js', ['jquery', 'intl-tel-input'], BIZMART_CORE_VERSION, true);

            wp_localize_script('bizmart-phone-js', 'BizmartPhoneData', [
                'initialCountry' => bizmart_get_option('biz_company_country', 'dz'),
                'phone' => bizmart_get_option('biz_company_phone', ''),
                'whatsapp' => bizmart_get_option('biz_company_whatsapp', ''),
            ]);
        }

        do_action('bizmart_base_assets_enqueued');
    }
}

add_action('admin_enqueue_scripts', function() {
    $screen = function_exists('get_current_screen') ? get_current_screen() : null;
    $is_biz_page = false;

    if ($screen && strpos((string)$screen->id, 'bizmart') !== false) $is_biz_page = true;
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    if (!$is_biz_page && !empty($_GET['page']) && strpos(sanitize_text_field(wp_unslash($_GET['page'])), 'bizmart') !== false) $is_biz_page = true;
    if (!$is_biz_page) return;

    // Enqueue Chart.js early (before wp_head) so it's available for dashboard & orders pages
    wp_deregister_script('bizmart-chartjs');
    wp_enqueue_script('bizmart-chartjs', BIZMART_CORE_URL . 'assets/vendor/chart.umd.min.js', [], '4.4.3', true);
    wp_deregister_script('bizmart-chartjs-zoom');
    wp_enqueue_script('bizmart-chartjs-zoom', BIZMART_CORE_URL . 'assets/vendor/chartjs-plugin-zoom.min.js', ['bizmart-chartjs'], '2.0.0', true);



    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
    bizmart_enqueue_base_admin_assets($page);

    // Products page assets (Core owns)
    if (($screen && strpos((string)$screen->id, 'bizmart-products') !== false) || (strpos($page, 'bizmart-products') !== false)) {
        wp_enqueue_media();

        wp_enqueue_style('bizmart-select2', BIZMART_CORE_URL . 'assets/vendor/select2.min.css', [], '4.1.0');
        wp_enqueue_script('bizmart-select2', BIZMART_CORE_URL . 'assets/vendor/select2.min.js', ['jquery'], '4.1.0', false);
        if (function_exists('wp_script_add_data')) {
            wp_script_add_data('bizmart-select2', 'group', 0);
        }

        wp_enqueue_style('bizmart-products-css', BIZMART_CORE_URL . 'assets/products.css', ['bizmart-admin-css'], BIZMART_CORE_VERSION);

        wp_enqueue_script('bizmart-products-js', BIZMART_CORE_URL . 'assets/products.js', ['jquery', 'bizmart-select2'], BIZMART_CORE_VERSION, false);
        if (function_exists('wp_script_add_data')) {
            wp_script_add_data('bizmart-products-js', 'group', 0);
        }
        wp_localize_script('bizmart-products-js', 'BizmartProductsData', [
            'adminLang' => bizmart_admin_lang(),
            'ajaxUrl'   => admin_url('admin-ajax.php'),
            'nonce'      => wp_create_nonce('bizmart-ajax-nonce'),
        ]);

        wp_enqueue_script('bizmart-products-list-js', BIZMART_CORE_URL . 'assets/js/admin-products-list.js', ['jquery', 'bizmart-products-js'], BIZMART_CORE_VERSION, true);
        wp_localize_script('bizmart-products-list-js', 'BizmartProductsListData', [
            'ajaxUrl' => admin_url('admin-ajax.php'),
            'nonce'   => wp_create_nonce('bizmart-ajax-nonce'),
        ]);
    }

    /**
     * Hook for addons to enqueue page-specific assets after core assets.
     */
    do_action('bizmart_after_core_admin_assets', $screen, $page);
});

/* ─── WooCommerce: Purchase price field (Core owns — addon must NOT re-register) ── */

/**
 * Core flag to prevent addon from re-registering these hooks.
 */
if (!defined('BIZMART_CORE_WC_HOOKS_REGISTERED')) {
    define('BIZMART_CORE_WC_HOOKS_REGISTERED', true);
}

if (!function_exists('bizmart_core_wc_add_purchase_price_field')) {
    function bizmart_core_wc_add_purchase_price_field(): void {
        if (!function_exists('woocommerce_wp_text_input')) return;

        woocommerce_wp_text_input([
            'id'                => '_purchase_price',
            'label'             => bizmart_text('Purchase price', "Prix d'achat", 'سعر الشراء'),
            'desc_tip'          => true,
            'description'       => bizmart_text('Purchase cost used for margin calculation.', "Coût d'achat utilisé pour le calcul de marge.", 'تكلفة الشراء المستخدمة لحساب هامش الربح.'),
            'type'              => 'number',
            'custom_attributes' => ['step' => '0.01', 'min' => '0'],
        ]);
    }
}
add_action('woocommerce_product_options_pricing', 'bizmart_core_wc_add_purchase_price_field');

if (!function_exists('bizmart_core_wc_save_purchase_price')) {
    function bizmart_core_wc_save_purchase_price(int $post_id): void {
        if (!isset($_POST['_purchase_price'])) return;
        if (!function_exists('wc_clean')) return;
        if (!isset($_POST['woocommerce_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['woocommerce_meta_nonce'])), 'woocommerce_save_data')) return;

        $price = sanitize_text_field(wp_unslash($_POST['_purchase_price']));
        update_post_meta($post_id, '_purchase_price', $price);

        // Keep internal products table in sync by SKU (best-effort)
        bizmart_sync_purchase_price_to_products_table($post_id, $price);
    }
}
add_action('woocommerce_process_product_meta', 'bizmart_core_wc_save_purchase_price');

if (!function_exists('bizmart_core_wc_add_variation_purchase_price_field')) {
    function bizmart_core_wc_add_variation_purchase_price_field($loop, $variation_data, $variation): void {
        if (!function_exists('woocommerce_wp_text_input')) return;

        woocommerce_wp_text_input([
            'id'                => "_purchase_price[{$loop}]",
            'wrapper_class'     => 'form-row form-row-first',
            'label'             => bizmart_text('Purchase price', "Prix d'achat", 'سعر الشراء'),
            'desc_tip'          => true,
            'description'       => bizmart_text('Purchase cost for this variation', "Coût d'achat pour cette variation", 'تكلفة الشراء لهذه الاختلافة'),
            'type'              => 'number',
            'value'             => isset($variation->ID) ? get_post_meta($variation->ID, '_purchase_price', true) : '',
            'custom_attributes' => ['step' => '0.01', 'min' => '0'],
        ]);
    }
}
add_action('woocommerce_variation_options_pricing', 'bizmart_core_wc_add_variation_purchase_price_field', 10, 3);

if (!function_exists('bizmart_core_wc_save_variation_purchase_price')) {
    function bizmart_core_wc_save_variation_purchase_price(int $variation_id, int $i): void {
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified by WooCommerce save handler
        if (!isset($_POST['_purchase_price'][$i])) return;
        if (!function_exists('wc_clean')) return;

        // Nonce already verified by WooCommerce's variation save handler
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified by WooCommerce save handler
        $price = sanitize_text_field(wp_unslash($_POST['_purchase_price'][$i]));
        if (!is_numeric($price) && $price !== '') return; // reject non-numeric junk
        update_post_meta($variation_id, '_purchase_price', $price);
    }
}
add_action('woocommerce_save_product_variation', 'bizmart_core_wc_save_variation_purchase_price', 10, 2);

/* ─── AJAX handlers (Core owns — addon must NOT re-register these) ──── */

// AJAX: Load product form modal
add_action('wp_ajax_bizmart_load_product_form', function() {
    bizmart_check_ajax_nonce('nonce');
    
    if (!current_user_can('manage_woocommerce')) {
        wp_send_json_error(bizmart_text('Insufficient permissions', 'Permissions insuffisantes', 'أذونات غير كافية'));
    }
    
    ob_start();
    include BIZMART_CORE_DIR . 'admin/product-form-ajax.php';
    $form_html = ob_get_clean();
    
    wp_send_json_success($form_html);
});

// AJAX: Get purchase price for a product
add_action('wp_ajax_bizmart_get_purchase_price', function() {
    bizmart_check_ajax_nonce('nonce');
    if (!current_user_can('manage_woocommerce')) wp_send_json_error('Unauthorized', 403);
    // phpcs:disable WordPress.Security.NonceVerification.Missing
    $product_id = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0;
    if (!$product_id) wp_send_json_error('Missing product_id', 400);
    if (!bizmart_wc_active()) wp_send_json_error('WooCommerce inactive', 400);
    $product = wc_get_product($product_id);
    if (!$product) wp_send_json_error('Product not found', 404);
    $purchase = get_post_meta($product_id, '_purchase_price', true);
    wp_send_json_success([ 'purchase_price' => $purchase, 'name' => $product->get_name() ]);
    // phpcs:enable
});

// AJAX: Set purchase price for a product
add_action('wp_ajax_bizmart_set_purchase_price', function() {
    bizmart_check_ajax_nonce('nonce');
    if (!current_user_can('manage_woocommerce')) wp_send_json_error('Unauthorized', 403);
    // phpcs:disable WordPress.Security.NonceVerification.Missing
    $product_id = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0;
    if (!$product_id) wp_send_json_error('Missing product_id', 400);
    $price_raw = isset($_POST['purchase_price']) ? sanitize_text_field(wp_unslash($_POST['purchase_price'])) : 0;
    $price = is_numeric($price_raw) ? round((float) $price_raw, 2) : 0;
    update_post_meta($product_id, '_purchase_price', $price);

    // Keep internal products table in sync by SKU
    bizmart_sync_purchase_price_to_products_table($product_id, $price);

    // If this is a variable product and "apply to all variations" is checked,
    // set the purchase price on every child variation too.
    $apply_to_vars = isset($_POST['apply_to_variations']) && sanitize_text_field(wp_unslash($_POST['apply_to_variations'])) === '1';
    if ($apply_to_vars && function_exists('wc_get_product')) {
        $product = wc_get_product($product_id);
        if ($product && $product->is_type('variable')) {
            foreach ($product->get_children() as $vid) {
                update_post_meta($vid, '_purchase_price', $price);
                bizmart_sync_purchase_price_to_products_table($vid, $price);
            }
        }
    }
    // phpcs:enable

    wp_send_json_success('Updated');
});

// AJAX: Get product details
add_action('wp_ajax_bizmart_get_product_details', function() {
    bizmart_check_ajax_nonce('nonce');
    if (!current_user_can('manage_woocommerce')) wp_send_json_error('Unauthorized', 403);
    // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $product_id = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0;
    if (!$product_id) wp_send_json_error('Missing product_id', 400);
    if (!bizmart_wc_active()) wp_send_json_error('WooCommerce inactive', 400);
    $product = wc_get_product($product_id);
    if (!$product) wp_send_json_error('Product not found', 404);
    $details = [
        'id' => $product->get_id(),
        'name' => $product->get_name(),
        'sku' => $product->get_sku(),
        'price' => $product->get_price(),
        'regular_price' => $product->get_regular_price(),
        'sale_price' => $product->get_sale_price(),
        'stock_quantity' => $product->get_stock_quantity(),
        'stock_status' => $product->get_stock_status(),
        'purchase_price' => get_post_meta($product_id, '_purchase_price', true),
        'type' => $product->get_type(),
        'permalink' => get_permalink($product_id),
        'edit_link' => admin_url('admin.php?page=bizmart-products&view=create&action=edit&id=' . $product_id),
        'image' => wp_get_attachment_image_url($product->get_image_id(), 'medium'),
    ];
    wp_send_json_success($details);
});

// AJAX: Create product (main handler - supports all product types)
// Core defines it; addon checks `has_action` and skips if already registered
add_action('wp_ajax_bizmart_create_product', function() {
        bizmart_check_ajax_nonce('nonce');
        if (!current_user_can('manage_woocommerce')) wp_send_json_error('Unauthorized');
        
        global $wpdb;
        
        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $raw_product_data = isset($_POST['product_data']) ? sanitize_text_field(wp_unslash($_POST['product_data'])) : '';
        $data = json_decode($raw_product_data, true);
        if (!$data || !is_array($data)) wp_send_json_error('Invalid data');

        $build_product_response = static function(int $product_id, array $source_data = []) : array {
            $product = function_exists('wc_get_product') ? wc_get_product($product_id) : null;
            $type = $product ? $product->get_type() : sanitize_key((string) ($source_data['type'] ?? 'simple'));
            $name = $product ? $product->get_name() : sanitize_text_field((string) ($source_data['name'] ?? ''));
            $sku = $product ? $product->get_sku() : sanitize_text_field((string) ($source_data['sku'] ?? ''));

            $purchase_raw = get_post_meta($product_id, '_purchase_price', true);
            if ($purchase_raw === '' || $purchase_raw === null) {
                $purchase_raw = $source_data['cost_price'] ?? ($source_data['base_cost_price'] ?? 0);
            }

            $selling_raw = get_post_meta($product_id, '_regular_price', true);
            if ($selling_raw === '' || $selling_raw === null) {
                $selling_raw = get_post_meta($product_id, '_price', true);
            }
            if (($selling_raw === '' || $selling_raw === null) && array_key_exists('regular_price', $source_data)) {
                $selling_raw = $source_data['regular_price'];
            }
            if (($selling_raw === '' || $selling_raw === null) && array_key_exists('base_regular_price', $source_data)) {
                $selling_raw = $source_data['base_regular_price'];
            }

            $manage_stock = $product ? (bool) $product->get_manage_stock() : !empty($source_data['manage_stock']);
            $stock_quantity = $product ? $product->get_stock_quantity() : (isset($source_data['stock_quantity']) ? (float) $source_data['stock_quantity'] : null);
            $stock_status = $product ? $product->get_stock_status() : sanitize_key((string) ($source_data['stock_status'] ?? 'instock'));
            $has_variations = false;
            if ($type === 'variable' && $product && method_exists($product, 'get_children')) {
                $has_variations = !empty($product->get_children());
            }

            return [
                'id' => $product_id,
                'name' => $name,
                'sku' => $sku,
                'type' => $type,
                'purchase_price' => is_numeric($purchase_raw) ? (float) $purchase_raw : 0.0,
                'selling_price' => is_numeric($selling_raw) ? (float) $selling_raw : 0.0,
                'manage_stock' => $manage_stock,
                'stock_quantity' => ($stock_quantity !== null && $stock_quantity !== '') ? (float) $stock_quantity : null,
                'stock_status' => $stock_status ?: 'instock',
                'has_variations' => $has_variations,
                'edit_link' => admin_url('admin.php?page=bizmart-products&view=create&action=edit&id=' . $product_id),
                'view_link' => get_permalink($product_id),
            ];
        };
        
        // UPDATE MODE: If an ID is provided, update the existing product
        $existing_id = isset($data['id']) ? absint($data['id']) : (isset($data['product_id']) ? absint($data['product_id']) : 0);
        if ($existing_id > 0) {
            $post = get_post($existing_id);
            if (!$post || !in_array($post->post_type, ['product', 'product_variation'], true)) {
                wp_send_json_error('Invalid product for update');
            }
            if (!current_user_can('edit_post', $existing_id)) {
                wp_send_json_error('Unauthorized');
            }

            $requested_type = isset($data['type']) ? sanitize_key((string) $data['type']) : 'simple';
            $wc_product_type = in_array($requested_type, ['simple', 'variable', 'grouped', 'external'], true)
                ? $requested_type
                : 'simple';

            // Normalize pricing fields across tabs
            $regular_price = $data['regular_price'] ?? ($data['price'] ?? ($data['base_regular_price'] ?? ''));
            $sale_price    = $data['sale_price'] ?? ($data['base_sale_price'] ?? '');
            $cost_price    = $data['cost_price'] ?? ($data['base_cost_price'] ?? '');

            $name = sanitize_text_field($data['name'] ?? '');
            if ($name === '') wp_send_json_error('Product name required');

            wp_update_post([
                'ID'           => $existing_id,
                'post_title'   => $name,
                'post_status'  => $data['status'] ?? $post->post_status,
                'post_content' => isset($data['description']) ? wp_kses_post($data['description']) : $post->post_content,
                'post_excerpt' => isset($data['short_description']) ? wp_kses_post($data['short_description']) : $post->post_excerpt,
            ]);

            $regular_price_f = ($regular_price === '' || $regular_price === null) ? '' : (function_exists('wc_format_decimal') ? wc_format_decimal($regular_price) : (string) ((float) $regular_price));
            $sale_price_f    = ($sale_price === '' || $sale_price === null) ? '' : (function_exists('wc_format_decimal') ? wc_format_decimal($sale_price) : (string) ((float) $sale_price));
            $cost_price_f    = ($cost_price === '' || $cost_price === null) ? '' : (function_exists('wc_format_decimal') ? wc_format_decimal($cost_price) : (string) ((float) $cost_price));

            if (isset($data['sku']) && $data['sku'] !== '') {
                update_post_meta($existing_id, '_sku', sanitize_text_field((string) $data['sku']));
            }

            if ($regular_price_f !== '') {
                update_post_meta($existing_id, '_regular_price', $regular_price_f);
                update_post_meta($existing_id, '_price', $regular_price_f);
            }
            update_post_meta($existing_id, '_sale_price', $sale_price_f);
            if ($sale_price_f !== '') {
                update_post_meta($existing_id, '_price', $sale_price_f);
            }

            // Purchase price - update even if manage_stock is off
            if ($cost_price_f !== '') {
                update_post_meta($existing_id, '_purchase_price', $cost_price_f);
            }

            $manage_stock = !empty($data['manage_stock']) ? 'yes' : 'no';
            update_post_meta($existing_id, '_manage_stock', $manage_stock);

            if ($manage_stock === 'yes') {
                $stock_qty = isset($data['stock_quantity']) ? (int) $data['stock_quantity'] : (int) get_post_meta($existing_id, '_stock', true);
                $stock_status = isset($data['stock_status']) ? sanitize_key((string) $data['stock_status']) : (string) get_post_meta($existing_id, '_stock_status', true);
                if ($stock_status === '') $stock_status = 'instock';
                update_post_meta($existing_id, '_stock', $stock_qty);
                update_post_meta($existing_id, '_stock_status', $stock_status);
            }

            update_post_meta($existing_id, '_type', $requested_type);
            if (taxonomy_exists('product_type')) {
                wp_set_object_terms($existing_id, $wc_product_type, 'product_type', false);
            }

            bizmart_set_product_categories($existing_id, $data);
            bizmart_set_product_tags($existing_id, $data);
            bizmart_set_product_media($existing_id, $data);
            bizmart_set_product_type_meta($existing_id, $wc_product_type, $data);
            bizmart_set_downloadable_files($existing_id, $requested_type, $data);

            if ($wc_product_type === 'variable') {
                bizmart_setup_variable_product($existing_id, $data);
            }

            // Sync top-level barcodes
            if (isset($data['barcodes']) && is_array($data['barcodes'])) {
                if (function_exists('bizmart_sync_product_barcodes')) {
                    bizmart_sync_product_barcodes($existing_id, $data['barcodes']);
                }
            }

            clean_post_cache($existing_id);
            if (function_exists('wc_delete_product_transients')) wc_delete_product_transients($existing_id);
            if (function_exists('bizmart_invalidate_product_metrics_cache')) bizmart_invalidate_product_metrics_cache($existing_id, 'product_update');

            wp_send_json_success($build_product_response($existing_id, $data));
            return;
        }

        // CREATE MODE: Insert new product using WP API for proper hook firing
        try {
            $product_id = wp_insert_post([
                'post_title'   => sanitize_text_field($data['name']),
                'post_type'    => 'product',
                'post_status'  => $data['status'] ?? 'publish',
                'post_author'  => get_current_user_id(),
                'post_content' => isset($data['description']) ? wp_kses_post($data['description']) : '',
                'post_excerpt' => isset($data['short_description']) ? wp_kses_post($data['short_description']) : '',
            ], true);
            
            if (is_wp_error($product_id)) {
                throw new Exception($product_id->get_error_message());
            }
            
            $requested_type = isset($data['type']) ? sanitize_key((string) $data['type']) : 'simple';
            $wc_product_type = in_array($requested_type, ['simple', 'variable', 'grouped', 'external'], true)
                ? $requested_type
                : 'simple';

            $regular_price = $data['regular_price'] ?? ($data['price'] ?? ($data['base_regular_price'] ?? ''));
            $sale_price = $data['sale_price'] ?? ($data['base_sale_price'] ?? '');
            $cost_price = $data['cost_price'] ?? ($data['base_cost_price'] ?? '');

            $regular_price_f = ($regular_price === '' || $regular_price === null) ? '' : (function_exists('wc_format_decimal') ? wc_format_decimal($regular_price) : (string) ((float) $regular_price));
            $sale_price_f    = ($sale_price === '' || $sale_price === null) ? '' : (function_exists('wc_format_decimal') ? wc_format_decimal($sale_price) : (string) ((float) $sale_price));
            $cost_price_f    = ($cost_price === '' || $cost_price === null) ? '' : (function_exists('wc_format_decimal') ? wc_format_decimal($cost_price) : (string) ((float) $cost_price));

            $meta_data = [
                '_sku'             => sanitize_text_field($data['sku'] ?? ''),
                '_price'           => $sale_price_f !== '' ? $sale_price_f : $regular_price_f,
                '_regular_price'   => $regular_price_f,
                '_sale_price'      => $sale_price_f,
                '_purchase_price'  => $cost_price_f,
                '_manage_stock'    => !empty($data['manage_stock']) ? 'yes' : 'no',
                '_stock'           => (int)($data['stock_quantity'] ?? 0),
                '_stock_status'    => isset($data['stock_status']) ? sanitize_key((string) $data['stock_status']) : 'instock',
                '_type'            => $requested_type,
            ];
            
            foreach ($meta_data as $meta_key => $meta_value) {
                add_post_meta($product_id, $meta_key, $meta_value, true);
            }

            if (taxonomy_exists('product_type')) {
                wp_set_object_terms($product_id, $wc_product_type, 'product_type', false);
            }

            bizmart_set_product_categories($product_id, $data);
            bizmart_set_product_tags($product_id, $data);
            bizmart_set_product_media($product_id, $data);
            bizmart_set_product_type_meta($product_id, $wc_product_type, $data);
            bizmart_set_downloadable_files($product_id, $requested_type, $data);

            // Sync top-level barcodes
            if (isset($data['barcodes']) && is_array($data['barcodes'])) {
                if (function_exists('bizmart_sync_product_barcodes')) {
                    bizmart_sync_product_barcodes($product_id, $data['barcodes']);
                }
            }

            if ($wc_product_type === 'variable') {
                bizmart_setup_variable_product($product_id, $data);

                // Apply base pricing to all variations when provided (create mode only).
                $base_regular = $data['base_regular_price'] ?? '';
                $base_sale    = $data['base_sale_price'] ?? '';
                $base_cost    = $data['base_cost_price'] ?? '';
                if (($base_regular !== '' && $base_regular !== null) || ($base_sale !== '' && $base_sale !== null) || ($base_cost !== '' && $base_cost !== null)) {
                    $parent = bizmart_get_variable_product($product_id);
                    if ($parent) {
                        foreach ($parent->get_children() as $vid) {
                            $variation = wc_get_product($vid);
                            if (!$variation || !$variation->is_type('variation')) continue;
                            if ($base_regular !== '' && $base_regular !== null) {
                                $variation->set_regular_price(wc_format_decimal($base_regular));
                            }
                            if ($base_sale !== '' && $base_sale !== null) {
                                $variation->set_sale_price(wc_format_decimal($base_sale));
                            }
                            if ($base_cost !== '' && $base_cost !== null) {
                                update_post_meta((int) $vid, '_purchase_price', wc_format_decimal($base_cost));
                            }
                            $variation->save();
                        }
                    }
                }
            }

            clean_post_cache($product_id);
            if (function_exists('wc_delete_product_transients')) {
                wc_delete_product_transients($product_id);
            }
            
            wp_send_json_success($build_product_response($product_id, $data));
            
        } catch (Exception $e) {
            wp_send_json_error('Error: ' . $e->getMessage());
        }
    });

/* ─── Shared helpers (used by both Core & Addon) ─────────────────────── */
if (!function_exists('bizmart_str_to_upper')) {
    function bizmart_str_to_upper($text): string {
        $text = (string) $text;
        if ($text === '') return '';
        return function_exists('mb_strtoupper') ? mb_strtoupper($text, 'UTF-8') : strtoupper($text);
    }
}

if (!function_exists('bizmart_get_db_version')) {
    function bizmart_get_db_version(): int {
        return (int) bizmart_get_option('bizmart_db_version', 0);
    }
}

if (!function_exists('bizmart_set_db_version')) {
    function bizmart_set_db_version(int $version): void {
        bizmart_update_option('bizmart_db_version', $version);
    }
}

/**
 * Shared helper: sync purchase price to bizmart_products table by SKU.
 * Used by both product-save hooks and AJAX handlers to avoid duplication.
 */
if (!function_exists('bizmart_sync_purchase_price_to_products_table')) {
    function bizmart_sync_purchase_price_to_products_table(int $product_id, $price): void {
        $sku = get_post_meta($product_id, '_sku', true);
        if (!$sku) return;

        global $wpdb;
        $table = $wpdb->prefix . 'bizmart_products';
        if (!bizmart_table_exists($table)) return;

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $exists = $wpdb->get_var($wpdb->prepare("SELECT id FROM {$table} WHERE sku = %s", $sku));
        if ($exists) {
            $wpdb->update($table, ['default_purchase_price' => $price], ['sku' => $sku], ['%f'], ['%s']);
        } else {
            $wpdb->insert($table, [
                'sku' => $sku,
                'name' => get_the_title($product_id),
                'default_purchase_price' => $price,
            ], ['%s', '%s', '%f']);
        }
        // phpcs:enable
    }
}

/**
 * Shared helper: parse variation attribute string (e.g. "Color|Red, Size|XL")
 * Returns array like ['attribute_color' => ['raw' => 'Red', 'norm' => 'red']]
 */
if (!function_exists('bizmart_parse_variation_attributes')) {
    function bizmart_parse_variation_attributes(string $attr_string): array {
        $out = [];
        $pairs = array_filter(array_map('trim', explode(',', $attr_string)));
        foreach ($pairs as $pair) {
            $parts = array_map('trim', explode('|', $pair, 2));
            if (count($parts) !== 2) continue;
            $k = sanitize_title($parts[0]);
            $v = $parts[1];
            if ($k === '' || $v === '') continue;
            $out['attribute_' . $k] = [
                'raw' => $v,
                'norm' => sanitize_title($v),
            ];
        }
        return $out;
    }
}

/**
 * Shared helper: match a variation by attributes and apply price/stock/SKU data.
 */
if (!function_exists('bizmart_apply_variation_data')) {
    function bizmart_apply_variation_data(array $children_ids, array $vrow): void {
        $attrs_raw = isset($vrow['attributes']) ? (string) $vrow['attributes'] : '';
        $desired = $attrs_raw !== '' ? bizmart_parse_variation_attributes($attrs_raw) : [];
        if (empty($desired)) return;

        $match_id = 0;
        foreach ($children_ids as $child_id) {
            $child = wc_get_product($child_id);
            if (!$child || !$child->is_type('variation')) continue;
            $child_attrs = (array) $child->get_attributes();

            $ok = true;
            foreach ($desired as $k => $dv) {
                $current = isset($child_attrs[$k]) ? (string) $child_attrs[$k] : '';
                if ($current !== $dv['raw'] && sanitize_title($current) !== $dv['norm']) {
                    $ok = false;
                    break;
                }
            }
            if ($ok) { $match_id = (int) $child_id; break; }
        }

        if ($match_id <= 0) return;

        $variation = wc_get_product($match_id);
        if (!$variation || !$variation->is_type('variation')) return;

        if (!empty($vrow['regular_price'])) $variation->set_regular_price(wc_format_decimal($vrow['regular_price']));
        if (!empty($vrow['sale_price'])) $variation->set_sale_price(wc_format_decimal($vrow['sale_price']));
        if (!empty($vrow['cost_price'])) update_post_meta($match_id, '_purchase_price', wc_format_decimal($vrow['cost_price']));
        if (!empty($vrow['sku'])) $variation->set_sku(sanitize_text_field((string) $vrow['sku']));

        if (isset($vrow['stock_quantity']) && $vrow['stock_quantity'] !== '') {
            $variation->set_manage_stock(true);
            $variation->set_stock_quantity((int) $vrow['stock_quantity']);
            $variation->set_stock_status(((int) $vrow['stock_quantity']) > 0 ? 'instock' : 'outofstock');
        }

        if (!empty($vrow['image_id'])) $variation->set_image_id((int) $vrow['image_id']);
        $variation->save();

        if (isset($vrow['barcodes']) && is_array($vrow['barcodes'])) {
            if (function_exists('bizmart_sync_product_barcodes')) {
                bizmart_sync_product_barcodes($match_id, $vrow['barcodes']);
            }
        }
    }
}

/**
 * Shared helper: build WC_Product_Attribute[] from front-end attribute data.
 */
if (!function_exists('bizmart_build_wc_attributes')) {
    function bizmart_build_wc_attributes(array $attributes_data): array {
        $wc_attributes = [];
        $pos = 0;
        foreach ($attributes_data as $attr) {
            if (empty($attr['name']) || empty($attr['values']) || !is_array($attr['values'])) continue;
            $attr_name = sanitize_text_field((string) $attr['name']);
            $values = array_values(array_filter(array_map('sanitize_text_field', $attr['values'])));
            if ($attr_name === '' || empty($values)) continue;

            $wc_attr = new WC_Product_Attribute();
            $wc_attr->set_id(0);
            $wc_attr->set_name($attr_name);
            $wc_attr->set_options($values);
            $wc_attr->set_position($pos++);
            $wc_attr->set_visible(!empty($attr['visible']));
            $wc_attr->set_variation(!empty($attr['variation']));
            $wc_attributes[] = $wc_attr;
        }
        return $wc_attributes;
    }
}

/**
 * Shared helper: get a WC_Product_Variable instance (creates one if needed).
 */
if (!function_exists('bizmart_get_variable_product')) {
    function bizmart_get_variable_product(int $product_id): ?WC_Product_Variable {
        $product = wc_get_product($product_id);
        if ($product && $product->is_type('variable')) return $product;
        if (class_exists('WC_Product_Variable')) {
            $product = new WC_Product_Variable($product_id);
            if ($product->is_type('variable')) return $product;
        }
        return null;
    }
}

/**
 * Shared helper: generate all missing variations for a variable product.
 */
if (!function_exists('bizmart_create_all_product_variations')) {
    function bizmart_create_all_product_variations(int $product_id): void {
        if (!class_exists('WC_Data_Store')) return;
        $limit = 50;
        if (class_exists('\\Automattic\\WooCommerce\\Utilities\\Constants')) {
            $limit = (int) \Automattic\WooCommerce\Utilities\Constants::get_constant('WC_MAX_LINKED_VARIATIONS');
        }
        $ds = WC_Data_Store::load('product-variable');
        if (!is_callable([$ds, 'create_all_product_variations'])) return;
        $p = bizmart_get_variable_product($product_id);
        if ($p) {
            $ds->create_all_product_variations($p, $limit);
        }
    }
}

/**
 * Shared helper: set WC attributes on a variable product and generate variations,
 * then apply per-variation data rows if provided.
 *
 * @param int   $product_id   The variable product ID.
 * @param array $data         Full product data (expects 'attributes' and optional 'variations').
 */
if (!function_exists('bizmart_setup_variable_product')) {
    function bizmart_setup_variable_product(int $product_id, array $data): void {
        if (empty($data['attributes']) || !is_array($data['attributes'])) return;

        // 1. Build & save WC attributes
        $wc_attributes = bizmart_build_wc_attributes($data['attributes']);
        if (!empty($wc_attributes)) {
            $product = bizmart_get_variable_product($product_id);
            if ($product) {
                $product->set_attributes($wc_attributes);
                $product->save();
            }
        }

        // 2. Generate missing variations
        bizmart_create_all_product_variations($product_id);

        // 3. Apply per-variation data rows
        if (!empty($data['variations']) && is_array($data['variations'])) {
            $parent = bizmart_get_variable_product($product_id);
            if ($parent) {
                $children_ids = $parent->get_children();
                foreach ($data['variations'] as $vrow) {
                    bizmart_apply_variation_data($children_ids, $vrow);
                }
            }
        }
    }
}

/* ─── Shared table utilities (used by both Core & Addon) ─────────────── */

/* ─── Product-meta helpers (eliminate duplication in create/update) ──── */

/**
 * Assign product categories from front-end data.
 */
if (!function_exists('bizmart_set_product_categories')) {
    function bizmart_set_product_categories(int $product_id, array $data): void {
        if (empty($data['categories'])) return;
        $ids = $data['categories'];
        if (!is_array($ids)) $ids = [$ids];
        $ids = array_values(array_filter(array_map('intval', $ids)));
        if (!empty($ids) && taxonomy_exists('product_cat')) {
            wp_set_object_terms($product_id, $ids, 'product_cat', false);
        }
    }
}

/**
 * Assign product tags from front-end data.
 */
if (!function_exists('bizmart_set_product_tags')) {
    function bizmart_set_product_tags(int $product_id, array $data): void {
        if (empty($data['tags']) || !taxonomy_exists('product_tag')) return;
        $tags = $data['tags'];
        if (is_string($tags)) $tags = array_map('trim', explode(',', $tags));
        if (is_array($tags)) {
            $tags = array_values(array_filter(array_map('sanitize_text_field', $tags)));
            if (!empty($tags)) {
                wp_set_object_terms($product_id, $tags, 'product_tag', false);
            }
        }
    }
}

/**
 * Set featured image + gallery from front-end data.
 */
if (!function_exists('bizmart_set_product_media')) {
    function bizmart_set_product_media(int $product_id, array $data): void {
        if (!empty($data['image_id'])) {
            $img = (int) $data['image_id'];
            if ($img > 0) set_post_thumbnail($product_id, $img);
        }
        if (isset($data['gallery_ids'])) {
            $gallery = $data['gallery_ids'];
            if (is_array($gallery)) {
                $gallery = implode(',', array_filter(array_map('intval', $gallery)));
            }
            $gallery = sanitize_text_field((string) $gallery);
            if ($gallery !== '') {
                update_post_meta($product_id, '_product_image_gallery', $gallery);
            }
        }
    }
}

/**
 * Set external product URL / button text and grouped children.
 */
if (!function_exists('bizmart_set_product_type_meta')) {
    function bizmart_set_product_type_meta(int $product_id, string $wc_type, array $data): void {
        if ($wc_type === 'external') {
            if (!empty($data['external_url'])) update_post_meta($product_id, '_product_url', esc_url_raw((string) $data['external_url']));
            if (!empty($data['button_text']))  update_post_meta($product_id, '_button_text', sanitize_text_field((string) $data['button_text']));
        }
        if ($wc_type === 'grouped' && isset($data['grouped_products'])) {
            $children = $data['grouped_products'];
            if (!is_array($children)) $children = [$children];
            $children = array_values(array_filter(array_map('intval', $children)));
            update_post_meta($product_id, '_children', $children);
        }
    }
}

/**
 * Set downloadable file meta from front-end data.
 */
if (!function_exists('bizmart_set_downloadable_files')) {
    function bizmart_set_downloadable_files(int $product_id, string $requested_type, array $data): void {
        if ($requested_type !== 'digital' && empty($data['download_files'])) return;

        update_post_meta($product_id, '_downloadable', 'yes');
        update_post_meta($product_id, '_virtual', 'yes');

        if (isset($data['download_limit']) && $data['download_limit'] !== '') {
            update_post_meta($product_id, '_download_limit', (int) $data['download_limit']);
        }
        if (isset($data['download_expiry']) && $data['download_expiry'] !== '') {
            update_post_meta($product_id, '_download_expiry', (int) $data['download_expiry']);
        }

        $raw = (string) ($data['download_files'] ?? '');
        $ids = array_values(array_filter(array_map('intval', explode(',', $raw))));
        if (empty($ids)) return;

        $files = [];
        foreach ($ids as $aid) {
            $url = wp_get_attachment_url($aid);
            if (!$url) continue;
            $name = basename(wp_parse_url($url, PHP_URL_PATH) ?: $url);
            $files[md5($url)] = ['name' => $name, 'file' => $url];
        }
        if (!empty($files)) {
            update_post_meta($product_id, '_downloadable_files', $files);
        }
    }
}

/* ─── Shared table utilities (continued) ─────────────────────────────── */

if (!function_exists('bizmart_table_exists')) {
    /**
     * Check whether a database table exists (cached per-request via static var).
     *
     * @param string $table Fully-qualified table name or short name (auto-prefixed).
     * @return bool
     */
    function bizmart_table_exists(string $table): bool {
        global $wpdb;
        static $cache = [];

        // Auto-prepend prefix when a short name is passed.
        if (strpos($table, $wpdb->prefix) !== 0) {
            $table = $wpdb->prefix . $table;
        }

        if (isset($cache[$table])) {
            return $cache[$table];
        }

        // Sanitize: table names must be alphanumeric + underscores only.
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
            $cache[$table] = false;
            return false;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table;
        $cache[$table] = $result;
        return $result;
    }
}

if (!function_exists('bizmart_invalidate_table_cache')) {
    /**
     * Flush the per-request table-existence cache (call after CREATE/DROP TABLE).
     * Kept for API compatibility with addons; static cache auto-resets per request.
     */
    function bizmart_invalidate_table_cache(?string $table = null): void {
        // Static cache resets automatically per request.
        // For same-request invalidation after CREATE TABLE, callers should avoid caching.
    }
}

if (!function_exists('bizmart_get_table_columns')) {
    /**
     * Get column names for a table (cached per-request).
     *
     * @param string $table Fully-qualified table name.
     * @return string[]
     */
    function bizmart_get_table_columns(string $table): array {
        global $wpdb;
        static $col_cache = [];

        // Auto-prepend prefix when a short name is passed.
        if (strpos($table, $wpdb->prefix) !== 0) {
            $table = $wpdb->prefix . $table;
        }

        if (isset($col_cache[$table])) {
            return $col_cache[$table];
        }

        if (!bizmart_table_exists($table)) {
            return [];
        }

        // Table name already validated by bizmart_table_exists; safe for identifier use.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $cols = $wpdb->get_col("SHOW COLUMNS FROM `{$table}`", 0);
        $cols = is_array($cols) ? $cols : [];
        $col_cache[$table] = $cols;
        return $cols;
    }
}

if (!function_exists('bizmart_add_column_if_missing')) {
    /**
     * Add a column to a table if it does not already exist.
     *
     * @param string $table          Fully-qualified table name.
     * @param string $column         Column name.
     * @param string $definition_sql e.g. "VARCHAR(100) DEFAULT NULL".
     */
    function bizmart_add_column_if_missing(string $table, string $column, string $definition_sql): void {
        global $wpdb;

        // Auto-prepend prefix when a short name is passed.
        if (strpos($table, $wpdb->prefix) !== 0) {
            $table = $wpdb->prefix . $table;
        }

        $cols = bizmart_get_table_columns($table);
        if (!$cols) return;
        if (in_array($column, $cols, true)) return;

        // Validate column & table names (alphanumeric + underscores).
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $column) || !preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
            return;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $wpdb->query("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition_sql}");
    }
}

if (!function_exists('bizmart_add_index_if_missing')) {
    /**
     * Add an index to a table if it does not already exist.
     *
     * @param string $table      Fully-qualified table name.
     * @param string $index_name Index name.
     * @param string $index_sql  e.g. "INDEX idx_name (column_name)".
     */
    function bizmart_add_index_if_missing(string $table, string $index_name, string $index_sql): void {
        global $wpdb;

        // Auto-prepend prefix when a short name is passed.
        if (strpos($table, $wpdb->prefix) !== 0) {
            $table = $wpdb->prefix . $table;
        }

        if (!bizmart_table_exists($table)) return;

        // Validate identifiers.
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table) || !preg_match('/^[a-zA-Z0-9_]+$/', $index_name)) {
            return;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $existing = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(1) FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND INDEX_NAME = %s",
            $table, $index_name
        ));
        if (!$existing) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $wpdb->query("ALTER TABLE `{$table}` ADD {$index_sql}");
        }
    }
}

/* ─── Plugin row links ───────────────────────────────────────────────── */
add_filter('plugin_action_links_' . plugin_basename(__FILE__), function($links) {
    $settings = '<a href="' . esc_url(admin_url('admin.php?page=bizmart-settings')) . '">' . esc_html__('Settings', 'bizmart-cost-of-goods') . '</a>';
    array_unshift($links, $settings);
    return $links;
});

/* ─── Activation / Deactivation hooks ────────────────────────────────── */

register_activation_hook(__FILE__, function() {
    // Set initial version flag for upgrade detection
    if (!get_option('bizmart_core_installed_at')) {
        update_option('bizmart_core_installed_at', current_time('mysql'), true);
    }
    update_option('bizmart_core_version', BIZMART_CORE_VERSION, true);

    // Clear any stale caches
    if (function_exists('bizmart_invalidate_product_metrics_cache')) {
        bizmart_invalidate_product_metrics_cache(null, 'activation');
    }

    // Flush rewrite rules on next load
    set_transient('bizmart_flush_rewrite', 1, 60);

    /**
     * Fires when Bizmart Core is activated.
     * Addons can hook here to run their own activation logic.
     */
    do_action('bizmart_core_activated');
});

register_deactivation_hook(__FILE__, function() {
    // Clean up scheduled events
    wp_clear_scheduled_hook('bizmart_final_import_job_process');
    wp_clear_scheduled_hook('bizmart_final_cleanup_job_process');

    /**
     * Fires when Bizmart Core is deactivated.
     * Addons can hook here to run their own deactivation cleanup.
     */
    do_action('bizmart_core_deactivated');
});

// Flush rewrite rules if requested during activation
add_action('init', function() {
    if (get_transient('bizmart_flush_rewrite')) {
        delete_transient('bizmart_flush_rewrite');
        flush_rewrite_rules(false);
    }
}, 999);

// WooCommerce barcode edit tab panel output hook
add_action('woocommerce_product_data_panels', 'bizmart_render_barcodes_data_panel');
add_filter('woocommerce_product_data_tabs', 'bizmart_add_barcodes_product_tab');

/* ─── Addon API: Health check & compatibility ────────────────────────── */

/**
 * Provide a health-check array for addons to verify core readiness.
 * Usage: $health = apply_filters('bizmart_core_health', []);
 */
add_filter('bizmart_core_health', function($health) {
    return [
        'core_version'  => BIZMART_CORE_VERSION,
        'core_dir'      => BIZMART_CORE_DIR,
        'core_url'      => BIZMART_CORE_URL,
        'wc_active'     => bizmart_wc_active(),
        'php_version'   => PHP_VERSION,
        'wp_version'    => get_bloginfo('version'),
        'db_version'    => bizmart_get_db_version(),
        'admin_lang'    => bizmart_admin_lang(),
        'timestamp'     => current_time('mysql'),
    ];
});

/**
 * Allow addons to register themselves for mutual discovery.
 * Usage: add_filter('bizmart_registered_addons', function($addons) {
 *     $addons['my-addon'] = ['version' => '1.0', 'file' => __FILE__];
 *     return $addons;
 * });
 */
if (!function_exists('bizmart_get_registered_addons')) {
    function bizmart_get_registered_addons(): array {
        return (array) apply_filters('bizmart_registered_addons', []);
    }
}

/**
 * Fires after Bizmart Core has fully loaded all includes and hooks.
 * Addons should hook here (priority 10+) to ensure all core APIs are available.
 */
do_action('bizmart_core_loaded');
