<?php

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

require_once dirname(__FILE__) . '/../dmm/sql/dmm-schema.php';

require_once dirname(__FILE__) . '/brand-vitals-schema.php';

if (!function_exists('aieo_get_wp_collation')) {

function aieo_get_wp_collation() {
    global $wpdb;

    
    $collation = $wpdb->get_var(
        "SELECT TABLE_COLLATION FROM INFORMATION_SCHEMA.TABLES
         WHERE TABLE_SCHEMA = DATABASE()
         AND TABLE_NAME = '{$wpdb->posts}'
         LIMIT 1"
    );

    
    if (empty($collation) && !empty($wpdb->collate)) {
        $collation = $wpdb->collate;
    }

    
    if (empty($collation)) {
        $collation = 'utf8mb4_unicode_ci';
    }

    return $collation;
}

function aieo_get_wp_charset() {
    global $wpdb;

    
    $charset = $wpdb->get_var(
        "SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLUMNS
         WHERE TABLE_SCHEMA = DATABASE()
         AND TABLE_NAME = '{$wpdb->posts}'
         AND COLUMN_NAME = 'post_title'
         LIMIT 1"
    );

    
    if (empty($charset) && !empty($wpdb->charset)) {
        $charset = $wpdb->charset;
    }

    
    if (empty($charset)) {
        $charset = 'utf8mb4';
    }

    return $charset;
}

function aieo_get_charset_collate() {
    $charset = aieo_get_wp_charset();
    $collate = aieo_get_wp_collation();

    $charset_collate = '';

    if (!empty($charset)) {
        $charset_collate = "DEFAULT CHARACTER SET {$charset}";
    }

    if (!empty($collate)) {
        $charset_collate .= " COLLATE {$collate}";
    }

    return $charset_collate;
}

function aieo_dbdelta($sql) {
    if (!function_exists('dbDelta')) {
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    }
    $clean = array();
    foreach (preg_split('/\r\n|\r|\n/', (string) $sql) as $line) {
        
        
        
        $line = preg_replace('/(^|\s)--(\s.*)?$/', '', $line);
        if (trim($line) === '') {
            continue; 
        }
        $clean[] = rtrim($line);
    }
    return dbDelta(implode("\n", $clean));
}

} 

function aieo_drop_index_if_exists($index_name, $table_name) {
    global $wpdb;

    
    $index_exists = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
         WHERE table_schema = DATABASE()
         AND table_name = %s
         AND index_name = %s",
        $table_name,
        $index_name
    ));

    
    if ($index_exists > 0) {
        $wpdb->query("DROP INDEX {$index_name} ON {$table_name}");
    }
}

function aieo_create_index_if_not_exists($index_name, $table_name, $columns) {
    global $wpdb;

    
    $wpdb->suppress_errors(true);

    
    $index_exists = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
         WHERE table_schema = DATABASE()
         AND table_name = %s
         AND index_name = %s",
        $table_name,
        $index_name
    ));

    $wpdb->suppress_errors(false);

    
    if ($index_exists == 0) {
        $wpdb->query("CREATE INDEX {$index_name} ON {$table_name} {$columns}");
        return true;
    }

    return false;
}

function aieo_drop_function_if_exists($function_name) {
    global $wpdb;

    
    $function_exists = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES
         WHERE ROUTINE_SCHEMA = DATABASE()
         AND ROUTINE_NAME = %s
         AND ROUTINE_TYPE = 'FUNCTION'",
        $function_name
    ));

    
    if ($function_exists > 0) {
        $wpdb->query("DROP FUNCTION IF EXISTS {$function_name}");
    }
}

function aieo_drop_procedure_if_exists($procedure_name) {
    global $wpdb;

    
    $procedure_exists = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES
         WHERE ROUTINE_SCHEMA = DATABASE()
         AND ROUTINE_NAME = %s
         AND ROUTINE_TYPE = 'PROCEDURE'",
        $procedure_name
    ));

    
    if ($procedure_exists > 0) {
        $wpdb->query("DROP PROCEDURE IF EXISTS {$procedure_name}");
    }
}

function aieo_can_create_stored_routines() {
    global $wpdb;

    
    $log_bin = $wpdb->get_var("SELECT @@GLOBAL.log_bin");

    
    if ($log_bin == 0 || $log_bin === '0' || $log_bin === 'OFF') {
        return true;
    }

    
    $trust_creators = $wpdb->get_var("SELECT @@GLOBAL.log_bin_trust_function_creators");

    if ($trust_creators == 1) {
        return true;
    }

    
    
    
    $grants = $wpdb->get_results("SHOW GRANTS FOR CURRENT_USER");
    if (!empty($grants)) {
        foreach ($grants as $grant) {
            $grant_str = reset($grant);

            
            if (stripos($grant_str, 'SUPER') !== false ||
                stripos($grant_str, 'BINLOG_ADMIN') !== false) {
                return true;
            }

            
            if (stripos($grant_str, 'ALL PRIVILEGES') !== false &&
                stripos($grant_str, ' ON *.*') !== false) {
                return true;
            }
        }
    }

    return false;
}

function aieo_create_core_product_attributes_table()
{
    global $wpdb;

    
    $core_product_attributes_table_name = $wpdb->prefix . 'aieo_core_product_attributes';

    
    $charset_collate = aieo_get_charset_collate();

    $sql_table = "CREATE TABLE IF NOT EXISTS {$core_product_attributes_table_name} (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(255)  DEFAULT NULL,
    `brand` varchar(255)  DEFAULT NULL,
    `brand_id` int DEFAULT NULL,
    `brand_name` varchar(255)  DEFAULT NULL,
    `vendor` varchar(255)  DEFAULT NULL,
    `vendor_id` int DEFAULT NULL,
    `brand_slug` varchar(255)  DEFAULT NULL,
    `product_id` bigint DEFAULT NULL,
    `size` varchar(255)  DEFAULT NULL,
    `color` varchar(255)  DEFAULT NULL,
    `product_count` decimal(12,0) DEFAULT NULL,
    `product_stock` decimal(12,0) DEFAULT NULL,
    `parent_product_id` bigint DEFAULT NULL,
    `effective_product_id` bigint DEFAULT NULL,
    `supplier_id` bigint DEFAULT NULL,
    `supplier` varchar(255)  DEFAULT NULL,
    `brand_bias` decimal(5,2) DEFAULT NULL,
    `campaigns` text DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) {$charset_collate};";

    $wpdb->query($sql_table);

    
    $indexes = array(
        'aieo_idx_cpb_brands_brand_id' => '(brand_id)',
        'aieo_idx_cpb_brands_product' => '(product_id)',
        'aieo_idx_cpb_brands_parent_product' => '(parent_product_id)',
        'aieo_idx_cpb_effective' => '(effective_product_id)'
    );

    foreach ($indexes as $index_name => $columns) {
        aieo_create_index_if_not_exists($index_name, $core_product_attributes_table_name, $columns);
    }
}

function aieo_create_core_product_category_hierarchy_table()
{
  global $wpdb;

  
  $core_product_category_hierarchy_table_name = $wpdb->prefix . 'aieo_core_product_category_hierarchy';

  
  $charset_collate = aieo_get_charset_collate();

  
  $sql_table = "CREATE TABLE IF NOT EXISTS {$core_product_category_hierarchy_table_name} (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255)   DEFAULT NULL,
  `product_id` bigint DEFAULT NULL,
  `top_category_id` int DEFAULT NULL,
  `top_category` varchar(150)   DEFAULT NULL,
  `second_level_category_id` int DEFAULT NULL,
  `second_level_category` varchar(150)   DEFAULT NULL,
  `third_level_category_id` int DEFAULT NULL,
  `third_level_category` varchar(255)   DEFAULT NULL,
  `fourth_level_category_id` int DEFAULT NULL,
  `fourth_level_category` varchar(255)   DEFAULT NULL,
  `fifth_level_category_id` int DEFAULT NULL,
  `fifth_level_category` varchar(255)   DEFAULT NULL,
  `reference_quantity` int DEFAULT NULL,
  `season_start` date DEFAULT NULL,
  `season_end` date DEFAULT NULL,
  `season_midpoint` date DEFAULT NULL,
  `campaigns` text DEFAULT NULL,
  `category_ids` varchar(500)  DEFAULT NULL,
  `tag_ids` varchar(230)  DEFAULT NULL,
  `tag_names` varchar(1000) ,
  `vendor` varchar(255)   DEFAULT NULL,
  `vendor_id` int DEFAULT NULL,
  `google_product_category` varchar(500) DEFAULT NULL,
  `facebook_product_category` varchar(500) DEFAULT NULL,
  PRIMARY KEY (`id`)
  ) {$charset_collate};";

  
  
  $wpdb->query($sql_table);

  
  
  
  
  
  if (function_exists('aieo_dmm_ensure_column')) {
    aieo_dmm_ensure_column($core_product_category_hierarchy_table_name, 'google_product_category',   'varchar(500) DEFAULT NULL');
    aieo_dmm_ensure_column($core_product_category_hierarchy_table_name, 'facebook_product_category', 'varchar(500) DEFAULT NULL');
  }

  
  $indexes = array(
    'aieo_idx_hierarchy_product_id' => '(product_id)',
    'aieo_idx_wppch_t_category_id' => '(top_category_id)',
    'aieo_idx_wppch_sl_category_id' => '(second_level_category_id)',
    'aieo_idx_wppch_tl_category_id' => '(third_level_category_id)'
  );

  foreach ($indexes as $index_name => $columns) {
    aieo_create_index_if_not_exists($index_name, $core_product_category_hierarchy_table_name, $columns);
  }
}

function aieo_create_core_category_brand_popularity_table()
{
  global $wpdb;

  
  $table_name = $wpdb->prefix . 'aieo_core_category_brand_popularity';

  
  $charset_collate = aieo_get_charset_collate();

  
  $sql_table = "CREATE TABLE IF NOT EXISTS {$table_name} (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,

    -- Category Identifiers
    `category_id` int DEFAULT NULL,
    `category_name` varchar(255) DEFAULT NULL,
    `category_slug` varchar(255) DEFAULT NULL,
    `category_level` tinyint DEFAULT NULL,
    `parent_category_id` int DEFAULT NULL,

    -- Brand Identifiers (NULL for category-only aggregates)
    `brand_id` int DEFAULT NULL,
    `brand_name` varchar(255) DEFAULT NULL,
    `brand_slug` varchar(255) DEFAULT NULL,

    -- Sales Metrics (Aggregated from store_orders)
    `total_sales` int DEFAULT 0,
    `total_turnover` decimal(12,2) DEFAULT 0.00,
    `total_orders` int DEFAULT 0,
    `product_count` int DEFAULT 0,

    -- Global Rankings
    `category_rank_by_sales` int DEFAULT NULL,
    `category_rank_by_turnover` int DEFAULT NULL,
    `brand_rank_by_sales` int DEFAULT NULL,
    `brand_rank_by_turnover` int DEFAULT NULL,

    -- Contextual Rankings (within parent/category/brand)
    `subcategory_rank_in_parent_sales` int DEFAULT NULL,
    `subcategory_rank_in_parent_turnover` int DEFAULT NULL,
    `brand_rank_in_category_sales` int DEFAULT NULL,
    `brand_rank_in_category_turnover` int DEFAULT NULL,
    `category_rank_in_brand_sales` int DEFAULT NULL,
    `category_rank_in_brand_turnover` int DEFAULT NULL,

    -- Metadata
    `last_updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    PRIMARY KEY (`id`),
    UNIQUE KEY `aieo_cbp_category_brand_unique` (`category_id`, `brand_id`),
    KEY `aieo_cbp_idx_category_id` (`category_id`),
    KEY `aieo_cbp_idx_brand_id` (`brand_id`),
    KEY `aieo_cbp_idx_parent_category` (`parent_category_id`),
    KEY `aieo_cbp_idx_cat_rank_sales` (`category_rank_by_sales`),
    KEY `aieo_cbp_idx_subcat_rank` (`parent_category_id`, `subcategory_rank_in_parent_sales`),
    KEY `aieo_cbp_idx_brand_in_cat` (`category_id`, `brand_rank_in_category_sales`)
  ) {$charset_collate};";

  
  $wpdb->query($sql_table);

  
  if ($wpdb->last_error) {
    aieo_debug_log('AIEO: Error creating category_brand_popularity table: ' . $wpdb->last_error);
  } else {
    aieo_debug_log('AIEO: Successfully created/verified category_brand_popularity table');
  }
}

function aieo_create_core_product_vitals_table()
{
  global $wpdb;

  
  $core_product_vitals_table_name = $wpdb->prefix . 'aieo_core_product_vitals';

  
  $charset_collate = aieo_get_charset_collate();

  
  $sql_table = "CREATE TABLE IF NOT EXISTS {$core_product_vitals_table_name} (
      `id` bigint unsigned NOT NULL AUTO_INCREMENT,
      `image_url` longtext  ,
      `name` varchar(255)   DEFAULT NULL,
      `product_id` bigint DEFAULT NULL,
      `parent_id` bigint DEFAULT NULL,
      `parent_product_id` bigint DEFAULT NULL,
      `category_ids` varchar(500)  DEFAULT NULL,
      `product_creation_date` datetime DEFAULT NULL,
      `product_age` int DEFAULT NULL,
      `product_first_sale_date` datetime DEFAULT NULL,
      `product_latest_sale_date` datetime DEFAULT NULL,
      `parent_name` varchar(255)   DEFAULT NULL,
      `parent_sku` varchar(200)   DEFAULT NULL,
      `category_id` int DEFAULT NULL,
      `brand_id` int DEFAULT NULL,
      `brand` varchar(255)   DEFAULT NULL,
      `supplier_id` bigint DEFAULT NULL,
      `supplier` varchar(255) DEFAULT NULL,
      `menu_id` int DEFAULT NULL,
      `menu_category` varchar(255)   DEFAULT NULL,
      `head_id` int DEFAULT NULL,
      `head_category` varchar(255)   DEFAULT NULL,
      `subcat_id` int DEFAULT NULL,
      `sub_category` varchar(255)   DEFAULT NULL,
      `reference_quantity` decimal(12,0) DEFAULT NULL,
      `brand_bias` decimal(5,2) DEFAULT NULL,
      `campaigns` text DEFAULT NULL,
      `season_start` date DEFAULT NULL,
      `season_end` date DEFAULT NULL,
      `season_midpoint` date DEFAULT NULL,
      `terminated_product` varchar(50) DEFAULT NULL,
      `replacement_product` varchar(50) DEFAULT NULL,
      `sku` varchar(200)   DEFAULT NULL,
      `gtin` varchar(40)   DEFAULT NULL,
      `sku_index` text DEFAULT NULL,
      `barcode_index` text DEFAULT NULL,
      `soft1_id` varchar(50) DEFAULT NULL,
      `profdiscount` varchar(10) DEFAULT NULL,
      `price` decimal(10,2) DEFAULT NULL,
      `cost` decimal(10,2) DEFAULT NULL,
      `status` int DEFAULT NULL,
      `product_url` varchar(300)   DEFAULT NULL,
      `link_to_view` varchar(500)   DEFAULT NULL,
      `link_to_cart` longtext  ,
      `link_to_fav` varchar(255)   DEFAULT NULL,
      `short_description` text DEFAULT NULL,
      `weight` decimal(18,4) DEFAULT NULL,
      `color` varchar(255)   DEFAULT NULL,
      `stock` decimal(12,0) DEFAULT NULL,
      `stock_status` varchar(40)   DEFAULT NULL,
      `regular_price` decimal(10,2) DEFAULT NULL,
      `onsale` int DEFAULT NULL,
      `role_special_pricing` int  NULL,
      `product_name` varchar(255)   DEFAULT NULL,
      `parent_product_name` varchar(255)   DEFAULT NULL,
      `category_names` varchar(1000) ,
      `tag_ids` varchar(230)  DEFAULT NULL,
      `tag_names` varchar(1000) ,
      `product_description` longtext  ,
      `vendor` varchar(255) DEFAULT NULL,
      `vendor_id` int DEFAULT NULL,
      `brand_slug` varchar(255)   DEFAULT NULL,
      `length` decimal(18,4) DEFAULT NULL,
      `width` decimal(18,4) DEFAULT NULL,
      `height` decimal(18,4) DEFAULT NULL,
      `size` varchar(255)   DEFAULT NULL,
      `image_gallery_urls` longtext  ,
      `erp_id` varchar(50)   DEFAULT NULL,
      `content_created` datetime DEFAULT CURRENT_TIMESTAMP,
      `content_last_updated` datetime DEFAULT CURRENT_TIMESTAMP,
      `ProdIdGraphUUID` BINARY(16),
      `PPIdGraphUUID` BINARY(16),
      `is_variation_parent` int DEFAULT 0,
      `variation_count` int DEFAULT 0,
      `in_stock_variation_offset` varchar(10) DEFAULT NULL,
      `variation_selector` int DEFAULT NULL,
      `variation_attribute_description` varchar(500) DEFAULT NULL,
      `variation_swatch` varchar(500) DEFAULT NULL,
      `google_product_category` varchar(500) DEFAULT NULL,
      `facebook_product_category` varchar(500) DEFAULT NULL,
      `is_auto_restocked` tinyint(1) DEFAULT 0,
      PRIMARY KEY (`id`)
  ) {$charset_collate};";

  
  $wpdb->query($sql_table);

  
  
  
  
  
  
  if (function_exists('aieo_dmm_ensure_column')) {
    aieo_dmm_ensure_column($core_product_vitals_table_name, 'google_product_category',   'varchar(500) DEFAULT NULL');
    aieo_dmm_ensure_column($core_product_vitals_table_name, 'facebook_product_category', 'varchar(500) DEFAULT NULL');
    
    
    
    aieo_dmm_ensure_column($core_product_vitals_table_name, 'sku_index',     'text DEFAULT NULL');
    aieo_dmm_ensure_column($core_product_vitals_table_name, 'barcode_index', 'text DEFAULT NULL');
    
    
    
    
    
    aieo_dmm_ensure_column($core_product_vitals_table_name, 'is_auto_restocked', 'tinyint(1) DEFAULT 0');
    
    
    
    
    
    aieo_dmm_ensure_column($core_product_vitals_table_name, 'rating_value', 'decimal(3,2) DEFAULT NULL');
    aieo_dmm_ensure_column($core_product_vitals_table_name, 'rating_count', 'int(10) unsigned NOT NULL DEFAULT 0');
  }

  
  
  
  $purl_len = (int) $wpdb->get_var($wpdb->prepare(
    "SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS
     WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = 'product_url'",
    $core_product_vitals_table_name
  ));
  if ($purl_len > 0 && $purl_len < 300) {
    $wpdb->query("ALTER TABLE `{$core_product_vitals_table_name}` MODIFY `product_url` varchar(300) DEFAULT NULL");
  }

  
  $indexes = array(
    'aieo_idx_vitals_product_id' => '(product_id)',
    'aieo_idx_vitals_wppv_parent_product' => '(parent_product_id)',
    
    
    
    
    'idx_aieo_parent_id' => '(parent_id)',
    'aieo_idx_vitals_wppv_sku' => '(sku)',
    'aieo_idx_vitals_wppv_parent_sku' => '(parent_sku)',
    'aieo_idx_vitals_wppv_stock' => '(stock)',
    'aieo_idx_vitals_wppv_brand' => '(brand_id)',
    'aieo_idx_vitals_wppv_menu_id' => '(menu_id)',
    'aieo_idx_vitals_wppv_head_id' => '(head_id)',
    'aieo_idx_vitals_wppv_subcat_id' => '(subcat_id)'
  );

  foreach ($indexes as $index_name => $columns) {
    aieo_create_index_if_not_exists($index_name, $core_product_vitals_table_name, $columns);
  }
}

function aieo_create_core_variation_vitals_table()
{
  global $wpdb;

  
  $core_variation_vitals_table_name = $wpdb->prefix . 'aieo_core_variation_vitals';

  
  $charset_collate = aieo_get_charset_collate();

  
  $sql_table = "CREATE TABLE IF NOT EXISTS {$core_variation_vitals_table_name} (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(255)   DEFAULT NULL,
    `product_id` bigint DEFAULT NULL,
    `parent_id` bigint DEFAULT NULL,
    `product_creation_date` datetime DEFAULT NULL,
    `product_age` int DEFAULT NULL,
    `product_first_sale_date` datetime DEFAULT NULL,
    `product_latest_sale_date` datetime DEFAULT NULL,
    `sku` varchar(255)   DEFAULT NULL,
    `gtin` varchar(50)   DEFAULT NULL,
    `price` decimal(10,2) DEFAULT NULL,
    `stock` decimal(12,0) DEFAULT NULL,
    `special_price` varchar(10) DEFAULT NULL,
    `weight` decimal(18,4) DEFAULT NULL,
    `imagepath` varchar(255)   DEFAULT NULL,
    `soft1_id` varchar(50) DEFAULT NULL,
    `variation_attribute_description` varchar(500) DEFAULT NULL,
    `variation_swatch` varchar(500) DEFAULT NULL,
    `variation_selector` int DEFAULT NULL,
    `cost` decimal(10,2) DEFAULT NULL,
    `regular_price` decimal(10,2) DEFAULT NULL,
    `onsale` int DEFAULT NULL,
    `role_special_pricing` int  NULL,
    `length` decimal(18,4) DEFAULT NULL,
    `width` decimal(18,4) DEFAULT NULL,
    `height` decimal(18,4) DEFAULT NULL,
    `size` varchar(255)   DEFAULT NULL,
    `color` varchar(255)   DEFAULT NULL,
    `erp_id` varchar(50)   DEFAULT NULL,
    `content_created` datetime DEFAULT CURRENT_TIMESTAMP,
    `content_last_updated` datetime DEFAULT CURRENT_TIMESTAMP,
    `ProdIdGraphUUID` BINARY(16),
    PRIMARY KEY (`id`)
  ) {$charset_collate};";

  
  $wpdb->query($sql_table);

  
  $indexes = array(
    'aieo_idx_var_vitals_product_id' => '(product_id)',
    'aieo_idx_var_vitals_parent_id' => '(parent_id)',
    'aieo_idx_var_vitals_sku' => '(sku)',
    'aieo_idx_var_vitals_erpid' => '(erp_id)'
  );

  foreach ($indexes as $index_name => $columns) {
    aieo_create_index_if_not_exists($index_name, $core_variation_vitals_table_name, $columns);
  }
}

function aieo_create_core_reciprocal_products_table()
{
  global $wpdb;

  
  $core_reciprocal_products_table_name = $wpdb->prefix . 'aieo_core_reciprocal_products';

  
  $charset_collate = aieo_get_charset_collate();

  
  $sql_table = "CREATE TABLE IF NOT EXISTS {$core_reciprocal_products_table_name} (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `old_product_id` bigint DEFAULT NULL,
  `old_parent_product_id` bigint DEFAULT NULL,
  `product_id` bigint DEFAULT NULL,
  `parent_product_id` bigint DEFAULT NULL,
  `sku` varchar(100)  DEFAULT NULL,
  `replacement_cause` int DEFAULT NULL,
  `replacement_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
  ) {$charset_collate};";

  
  
  $wpdb->query($sql_table);

  
  $indexes = array(
    'aieo_idx_rpt_source_product_id' => '(old_product_id)',
    'aieo_idx_rpt_source_parent_product_id' => '(old_parent_product_id)',
    'aieo_idx_rpt_target_product_id' => '(product_id)',
    'aieo_idx_rpt_target_parent_product_id' => '(parent_product_id)',
    'aieo_idx_rpt_target_sku' => '(sku)'
  );

  foreach ($indexes as $index_name => $columns) {
    aieo_create_index_if_not_exists($index_name, $core_reciprocal_products_table_name, $columns);
  }
}

function aieo_create_temp_orders_table()
{
  global $wpdb;

  
  $temp_orders_table_name = $wpdb->prefix . 'aieo_temp_orders';

  
  $charset_collate = aieo_get_charset_collate();

  
  $sql_table = "CREATE TABLE IF NOT EXISTS {$temp_orders_table_name} (
        `Id` int NOT NULL AUTO_INCREMENT,
        `OrderId` bigint unsigned NOT NULL,
        `OrderStatus` varchar(40)  DEFAULT NULL,
        `SKU` varchar(230)  DEFAULT NULL,
        `ParentSKU` varchar(200)   DEFAULT NULL,
        `ProductId` int DEFAULT NULL,
        `ParentProductId` int DEFAULT NULL,
        `CategoryId` varchar(230)  DEFAULT NULL,
        `TagId` varchar(230)  DEFAULT NULL,
        `OrderDate` datetime DEFAULT NULL,
        `PrimaryQty` bigint unsigned NOT NULL DEFAULT '0',
        `ItemSequence` bigint unsigned NOT NULL DEFAULT '0',
        `Stock` int DEFAULT NULL,
        `OrderSize` bigint DEFAULT '0',
        `FinalTouchpoint` varchar(255)   DEFAULT NULL,
        `FinalTouchpointId` int DEFAULT NULL,
        `EngagementTime` int DEFAULT NULL,
        `JourneyLinearWeight` decimal(10,6) DEFAULT NULL,
        `AnonymousWeight` varchar(1)  NOT NULL DEFAULT '',
        `Price` decimal(10,2) DEFAULT NULL,
        `RegularPrice` decimal(10,2) DEFAULT NULL,
        `OnSale` int DEFAULT NULL,
        `RoleBasedSpecialPricing` int DEFAULT NULL,
        `Discontinued` int DEFAULT NULL,
        `HasReciprocal` int DEFAULT NULL,
        `LineValue` decimal(10,2) DEFAULT NULL,
        `OrderItemsTotal` decimal(10,2) DEFAULT NULL,
        `ValueLinearWeight` decimal(10,6) DEFAULT NULL,
        `CurrentPrice` decimal(10,2) DEFAULT NULL,
        `CurrentRegularPrice` decimal(10,2) DEFAULT NULL,
        `ItemName` longtext  NULL,
        `ParentItemName` longtext ,
        `ItemNameDiff` varchar(500)  DEFAULT NULL,
        `TotalItemSales` int DEFAULT NULL,
        `DistinctOrderSales` int DEFAULT NULL,
        `TotalTurnover` decimal(10,2) DEFAULT NULL,
        `UniqueItemSales` int DEFAULT NULL,
        `FirstItemSales` int DEFAULT NULL,
        `LastItemSales` int DEFAULT NULL,
        `AvgJLW` decimal(10,7) DEFAULT NULL,
        `AvgVLW` decimal(10,7) DEFAULT NULL,
        `ProfitabilityIndex` int DEFAULT NULL,
        `BrandId` int DEFAULT NULL,
        `Brand` varchar(255)  DEFAULT NULL,
        `BrandSlug` varchar(255)  DEFAULT NULL,
        `Vendor` varchar(255)   DEFAULT NULL,
        `VendorId` int DEFAULT NULL,
        `MenuId` INT DEFAULT NULL,
        `MenuCategory` varchar(255)   DEFAULT NULL,
        `HeadId` INT DEFAULT NULL,
        `HeadCategory` varchar(255)   DEFAULT NULL,
        `SubcatId` INT DEFAULT NULL,
        `SubCategory` varchar(255)   DEFAULT NULL,
        `Weight` decimal(18,4) DEFAULT NULL,
        `Length` decimal(18,4) DEFAULT NULL,
        `Width` decimal(18,4) DEFAULT NULL,
        `Height` decimal(18,4) DEFAULT NULL,
        `Size` varchar(255)   DEFAULT NULL,
        `Color` varchar(255)   DEFAULT NULL,
        `ImageUrl` longtext   DEFAULT NULL,
        `ImageGalleryUrl` longtext   DEFAULT NULL,
        `ProductUrl` varchar(255)   DEFAULT NULL,
        `ERPId` varchar(50)   DEFAULT NULL,
        `BrandAvgJLW` decimal(10,7) DEFAULT NULL,
        `BrandAvgVLW` decimal(10,7) DEFAULT NULL,
        `BrandTotalTurnover` decimal(10,7) DEFAULT NULL,
        `ContentWordCount` int DEFAULT NULL,
        `ContentIntro` longtext ,
        `ContentIntroPlain` longtext ,
        `ContentSentimentArray` varchar(300)  DEFAULT NULL,
        `ContentViews` int DEFAULT NULL,
        `ContentCreated` datetime DEFAULT CURRENT_TIMESTAMP,
        `ContentLastUpdated` datetime DEFAULT CURRENT_TIMESTAMP,
        `ContentIncoming` int DEFAULT NULL,
        `ContentOutgoing` int DEFAULT NULL,
        `ContentCorIndex` decimal(10,7) DEFAULT NULL,
        `CategoryName` longtext ,
        `CategoryCorIndex` decimal(10,7) DEFAULT NULL,
        `TagName` longtext ,
        `TagCorIndex` decimal(10,7) DEFAULT NULL,
        `KeyVerb` varchar(50)  DEFAULT NULL,
        `KeySentimentArray` varchar(500)  DEFAULT NULL,
        `EponymousID` int DEFAULT NULL,
        `LoyaltyID` int DEFAULT NULL,
        `LoyaltyPoints` int DEFAULT NULL,
        `EshopCustomerID` int DEFAULT NULL,
        `StoreCustomerID` int DEFAULT NULL,
        `CustomerEshopSince` datetime DEFAULT NULL,
        `CustomerLoyaltySince` datetime DEFAULT NULL,
        `CustomerStoreSince` datetime DEFAULT NULL,
        `CustomerName` varchar(150)   DEFAULT NULL,
        `CustomerEmail` varchar(80)   DEFAULT NULL,
        `CustomerPhone` varchar(30)   DEFAULT NULL,
        `CustomerPostalCode` varchar(50)   DEFAULT NULL,
        `CustomerLocality` varchar(50)   DEFAULT NULL,
        `AvgOrderSize` decimal(10,2) DEFAULT NULL,
        `MaxOrderSize` int DEFAULT NULL,
        `MinOrderSize` int  DEFAULT NULL,
        `SDOrderSize` DECIMAL(10,7) DEFAULT NULL, 
        `zOrderSize` DECIMAL(10,7) DEFAULT NULL, 
        `AvgSpend` decimal(10,2) DEFAULT NULL,
        `MaxSpend` decimal(10,2) DEFAULT NULL,
        `MinSpend` decimal(10,2) DEFAULT NULL,
        `SDSpend` DECIMAL(10,7) DEFAULT NULL,
        `zSpend` DECIMAL(10,7) DEFAULT NULL,
        `TotalCustomerItems` int DEFAULT NULL,
        `TotalCustomerOrders` int DEFAULT NULL,
        `TotalCustomerTurnover` decimal(10,2) DEFAULT NULL,
        `Fav1Product` int DEFAULT NULL,
        `Fav2Product` int DEFAULT NULL,
        `Fav3Product` int DEFAULT NULL,
        `Fav1Brand` int DEFAULT NULL,
        `Fav2Brand` int DEFAULT NULL,
        `Fav3Brand` int DEFAULT NULL,
        `Fav1MainCat` int DEFAULT NULL,
        `Fav2MainCat` int DEFAULT NULL,
        `Fav3MainCat` int DEFAULT NULL,
        `Fav1HeadCat` int DEFAULT NULL,
        `Fav2HeadCat` int DEFAULT NULL,
        `Fav3HeadCat` int DEFAULT NULL,
        `Fav1SubCat` int DEFAULT NULL,
        `Fav2SubCat` int DEFAULT NULL,
        `Fav3SubCat` int DEFAULT NULL,
        `Fav1Size` varchar(50)  DEFAULT NULL,
        `Fav2Size` varchar(50)  DEFAULT NULL,
        `Fav3Size` varchar(50)  DEFAULT NULL,
        `Fav1Color` varchar(50)  DEFAULT NULL,
        `Fav2Color` varchar(50)  DEFAULT NULL,
        `Fav3Color` varchar(50)  DEFAULT NULL,    
        `LastTransactionDate` datetime DEFAULT NULL,
        `LastTransactionLocation` varchar(80) DEFAULT NULL,
        `LastOrderSpend` decimal(10,2) DEFAULT NULL,
        `CurrentDBO` INT DEFAULT NULL,
        `AvgDBO` decimal(10,2) DEFAULT NULL,
        `MaxDBO` INT DEFAULT NULL,
        `MinDBO` INT DEFAULT NULL,
        `SDDBO` DECIMAL(10,7) DEFAULT NULL,  
        `zDBO` DECIMAL(10,7) DEFAULT NULL,
        `maDBO` DECIMAL(10,7) DEFAULT NULL,
        `Seasonality` varchar(1)  NOT NULL DEFAULT '',
        `PartnerId` int DEFAULT NULL,
        `OriginId` int DEFAULT NULL,
        `OriginType` varchar(50)  DEFAULT NULL,
        `EventId` int DEFAULT NULL,
        `EventType` varchar(50)  DEFAULT NULL,
        `EventLocation` varchar(255)  DEFAULT NULL,
        `AffiliateId` int DEFAULT NULL,
        `GASource` varchar(50)  DEFAULT NULL,
        `GAMedium` varchar(50)  DEFAULT NULL,
        `GACampaign` varchar(50)  DEFAULT NULL,
        `GAContent` varchar(50)  DEFAULT NULL,
        `GATerm` varchar(50)  DEFAULT NULL,
        `GABrand` varchar(50)  DEFAULT NULL,
        `CustomerDevice` varchar(100)  DEFAULT NULL,
        `CustomerSegment` varchar(100)  DEFAULT NULL,
        `CustomerFeedback` varchar(100)  DEFAULT NULL,
        `CustomerFeedforward` varchar(100)  DEFAULT NULL,
        `JourneyEventsArray` tinytext ,
        `JourneyDuration` int DEFAULT NULL,
        `HitType` int DEFAULT NULL,
        `HitRate` decimal(10,7) DEFAULT NULL,
        `Velocity` decimal(10,7) DEFAULT NULL,
        `Momentum` decimal(10,7) DEFAULT NULL,
        `Closeness` decimal(10,7) DEFAULT NULL,
        `AdamicAdar` decimal(10,7) DEFAULT NULL,
        `Jaccard` decimal(10,7) DEFAULT NULL,
        `Louvain` decimal(10,7) DEFAULT NULL,
        `BFS` decimal(10,7) DEFAULT NULL,
        `MotivesArray` decimal(10,7) DEFAULT NULL,
        `CalabiYau` decimal(10,7) DEFAULT NULL,
        `Perspective` varchar(230)  NOT NULL DEFAULT '',
        `Transparency` decimal(10,7) DEFAULT NULL,
        `ABTesting` varchar(40)  NOT NULL DEFAULT '',
        `Neo4jParams` tinytext ,
        `OrderIdGraphUUID` BINARY(16),
        `OrderItemIdGraphUUID` BINARY(16),
        `PPIdGraphUUID` BINARY(16),
        `ProdIdGraphUUID` BINARY(16),
        `EpoIdGraphUUID` BINARY(16),
        `O_PP_GraphUUID` BINARY(16),
        `O_PP_Freq` INT,
        `O_Prod_GraphUUID` BINARY(16),
        `O_Prod_Freq` INT,
        `O_Epo_GraphUUID` BINARY(16),
        `O_Epo_Freq` INT,
        `PP_Epo_GraphUUID` BINARY(16),
        `PP_Epo_Freq` INT,
        `Prod_Epo_GraphUUID` BINARY(16),
        `Prod_Epo_Freq` INT,
        PRIMARY KEY (`Id`)
    ) {$charset_collate};";

  
  
  $wpdb->query($sql_table);

  
  $indexes = array(
    "aieo_idx_orderid_orders" => "(OrderId)",
    "aieo_idx_productid_orders" => "(ProductId)",
    "aieo_idx_parentproductid_orders" => "(ParentProductId)",
    "aieo_idx_brandid_orders" => "(BrandId)",
    "aieo_idx_eponymousid_orders" => "(EponymousID)",
    "aieo_idx_loyaltyid_orders" => "(LoyaltyID)"
  );

  
  foreach ($indexes as $index_name => $columns) {
    aieo_drop_index_if_exists($index_name, $temp_orders_table_name);
    $wpdb->query("CREATE INDEX {$index_name} ON {$temp_orders_table_name} {$columns};");
  }
}

function aieo_create_temp_store_orders_table()
{
  global $wpdb;

  
  $temp_store_orders_table_name = $wpdb->prefix . 'aieo_temp_store_orders';

  
  $charset_collate = aieo_get_charset_collate();

  
  $sql_table = "CREATE TABLE IF NOT EXISTS {$temp_store_orders_table_name} (
        `Id` int NOT NULL AUTO_INCREMENT,
        `OrderId` bigint unsigned NOT NULL,
        `OrderStatus` varchar(40)  DEFAULT NULL,
        `SKU` varchar(230)  DEFAULT NULL,
        `ParentSKU` varchar(200)   DEFAULT NULL,
        `ProductId` int DEFAULT NULL,
        `ParentProductId` int DEFAULT NULL,
        `CategoryId` varchar(230)  DEFAULT NULL,
        `TagId` varchar(230)  DEFAULT NULL,
        `OrderDate` datetime DEFAULT NULL,
        `PrimaryQty` bigint unsigned NOT NULL DEFAULT '0',
        `ItemSequence` bigint unsigned NOT NULL DEFAULT '0',
        `Stock` int DEFAULT NULL,
        `OrderSize` bigint DEFAULT '0',
        `FinalTouchpoint` varchar(255)   DEFAULT NULL,
        `FinalTouchpointId` int DEFAULT NULL,
        `EngagementTime` int DEFAULT NULL,
        `JourneyLinearWeight` decimal(10,6) DEFAULT NULL,
        `AnonymousWeight` varchar(1)  NOT NULL DEFAULT '',
        `Price` decimal(10,2) DEFAULT NULL,
        `RegularPrice` decimal(10,2) DEFAULT NULL,
        `OnSale` int DEFAULT NULL,
        `RoleBasedSpecialPricing` int DEFAULT NULL,
        `Discontinued` int DEFAULT NULL,
        `HasReciprocal` int DEFAULT NULL,
        `LineValue` decimal(10,2) DEFAULT NULL,
        `OrderItemsTotal` decimal(10,2) DEFAULT NULL,
        `TotalOrderValue` decimal(20,6) DEFAULT NULL,
        `ValueLinearWeight` decimal(20,6) DEFAULT NULL,
        `CurrentPrice` decimal(10,2) DEFAULT NULL,
        `CurrentRegularPrice` decimal(10,2) DEFAULT NULL,
        `ItemName` longtext  NULL,
        `ParentItemName` longtext ,
        `ItemNameDiff` varchar(500)  DEFAULT NULL,
        `TotalItemSales` int DEFAULT NULL,
        `DistinctOrderSales` int DEFAULT NULL,
        `TotalTurnover` decimal(10,2) DEFAULT NULL,
        `UniqueItemSales` int DEFAULT NULL,
        `FirstItemSales` int DEFAULT NULL,
        `LastItemSales` int DEFAULT NULL,
        `AvgJLW` decimal(10,7) DEFAULT NULL,
        `AvgVLW` decimal(10,7) DEFAULT NULL,
        `ProfitabilityIndex` int DEFAULT NULL,
        `BrandId` int DEFAULT NULL,
        `Brand` varchar(255)  DEFAULT NULL,
        `BrandSlug` varchar(255)  DEFAULT NULL,
        `Vendor` varchar(255)   DEFAULT NULL,
        `VendorId` int DEFAULT NULL,
        `MenuId` INT DEFAULT NULL,
        `MenuCategory` varchar(255)   DEFAULT NULL,
        `HeadId` INT DEFAULT NULL,
        `HeadCategory` varchar(255)   DEFAULT NULL,
        `SubcatId` INT DEFAULT NULL,
        `SubCategory` varchar(255)   DEFAULT NULL,
        `Weight` decimal(18,4) DEFAULT NULL,
        `Length` decimal(18,4) DEFAULT NULL,
        `Width` decimal(18,4) DEFAULT NULL,
        `Height` decimal(18,4) DEFAULT NULL,
        `Size` varchar(255)   DEFAULT NULL,
        `Color` varchar(255)   DEFAULT NULL,
        `ImageUrl` longtext   DEFAULT NULL,
        `ImageGalleryUrl` longtext   DEFAULT NULL,
        `ProductUrl` varchar(255)   DEFAULT NULL,
        `ERPId` varchar(50)   DEFAULT NULL,
        `BrandAvgJLW` decimal(10,7) DEFAULT NULL,
        `BrandAvgVLW` decimal(10,7) DEFAULT NULL,
        `BrandTotalTurnover` decimal(10,7) DEFAULT NULL,
        `ContentWordCount` int DEFAULT NULL,
        `ContentIntro` longtext ,
        `ContentIntroPlain` longtext ,
        `ContentSentimentArray` varchar(300)  DEFAULT NULL,
        `ContentViews` int DEFAULT NULL,
        `ContentCreated` datetime DEFAULT CURRENT_TIMESTAMP,
        `ContentLastUpdated` datetime DEFAULT CURRENT_TIMESTAMP,
        `ContentIncoming` int DEFAULT NULL,
        `ContentOutgoing` int DEFAULT NULL,
        `ContentCorIndex` decimal(10,7) DEFAULT NULL,
        `CategoryName` longtext ,
        `CategoryCorIndex` decimal(10,7) DEFAULT NULL,
        `TagName` longtext ,
        `TagCorIndex` decimal(10,7) DEFAULT NULL,
        `KeyVerb` varchar(50)  DEFAULT NULL,
        `KeySentimentArray` varchar(500)  DEFAULT NULL,
        `EponymousID` int DEFAULT NULL,
        `LoyaltyID` int DEFAULT NULL,
        `LoyaltyPoints` int DEFAULT NULL,
        `EshopCustomerID` int DEFAULT NULL,
        `StoreCustomerID` int DEFAULT NULL,
        `CustomerEshopSince` datetime DEFAULT NULL,
        `CustomerLoyaltySince` datetime DEFAULT NULL,
        `CustomerStoreSince` datetime DEFAULT NULL,
        `CustomerName` varchar(150)   DEFAULT NULL,
        `CustomerEmail` varchar(80)   DEFAULT NULL,
        `CustomerPhone` varchar(30)   DEFAULT NULL,
        `CustomerPostalCode` varchar(50)   DEFAULT NULL,
        `CustomerLocality` varchar(50)   DEFAULT NULL,
        `AvgOrderSize` decimal(10,2) DEFAULT NULL,
        `MaxOrderSize` int DEFAULT NULL,
        `MinOrderSize` int  DEFAULT NULL,
        `SDOrderSize` DECIMAL(10,7) DEFAULT NULL, 
        `zOrderSize` DECIMAL(10,7) DEFAULT NULL, 
        `AvgSpend` decimal(10,2) DEFAULT NULL,
        `MaxSpend` decimal(10,2) DEFAULT NULL,
        `MinSpend` decimal(10,2) DEFAULT NULL,
        `SDSpend` DECIMAL(10,7) DEFAULT NULL,
        `zSpend` DECIMAL(10,7) DEFAULT NULL,
        `TotalCustomerItems` int DEFAULT NULL,
        `TotalCustomerOrders` int DEFAULT NULL,
        `TotalCustomerTurnover` decimal(10,2) DEFAULT NULL,
        `Fav1Product` int DEFAULT NULL,
        `Fav2Product` int DEFAULT NULL,
        `Fav3Product` int DEFAULT NULL,
        `Fav1Brand` int DEFAULT NULL,
        `Fav2Brand` int DEFAULT NULL,
        `Fav3Brand` int DEFAULT NULL,
        `Fav1MainCat` int DEFAULT NULL,
        `Fav2MainCat` int DEFAULT NULL,
        `Fav3MainCat` int DEFAULT NULL,
        `Fav1HeadCat` int DEFAULT NULL,
        `Fav2HeadCat` int DEFAULT NULL,
        `Fav3HeadCat` int DEFAULT NULL,
        `Fav1SubCat` int DEFAULT NULL,
        `Fav2SubCat` int DEFAULT NULL,
        `Fav3SubCat` int DEFAULT NULL,
        `Fav1Size` varchar(50)  DEFAULT NULL,
        `Fav2Size` varchar(50)  DEFAULT NULL,
        `Fav3Size` varchar(50)  DEFAULT NULL,
        `Fav1Color` varchar(50)  DEFAULT NULL,
        `Fav2Color` varchar(50)  DEFAULT NULL,
        `Fav3Color` varchar(50)  DEFAULT NULL,    
        `LastTransactionDate` datetime DEFAULT NULL,
        `LastTransactionLocation` varchar(80) DEFAULT NULL,
        `LastOrderSpend` decimal(10,2) DEFAULT NULL,
        `CurrentDBO` INT DEFAULT NULL,
        `AvgDBO` decimal(10,2) DEFAULT NULL,
        `MaxDBO` INT DEFAULT NULL,
        `MinDBO` INT DEFAULT NULL,
        `SDDBO` DECIMAL(10,7) DEFAULT NULL,  
        `zDBO` DECIMAL(10,7) DEFAULT NULL,
        `maDBO` DECIMAL(10,7) DEFAULT NULL,
        `Seasonality` int  DEFAULT NULL,
        `PartnerId` int DEFAULT NULL,
        `OriginId` int DEFAULT NULL,
        `OriginType` varchar(50)  DEFAULT NULL,
        `EventId` int DEFAULT NULL,
        `EventType` varchar(50)  DEFAULT NULL,
        `EventLocation` varchar(255)  DEFAULT NULL,
        `AffiliateId` int DEFAULT NULL,
        `GASource` varchar(50)  DEFAULT NULL,
        `GAMedium` varchar(50)  DEFAULT NULL,
        `GACampaign` varchar(50)  DEFAULT NULL,
        `GAContent` varchar(50)  DEFAULT NULL,
        `GATerm` varchar(50)  DEFAULT NULL,
        `GABrand` varchar(50)  DEFAULT NULL,
        `CustomerDevice` varchar(100)  DEFAULT NULL,
        `CustomerSegment` varchar(100)  DEFAULT NULL,
        `CustomerFeedback` varchar(100)  DEFAULT NULL,
        `CustomerFeedforward` varchar(100)  DEFAULT NULL,
        `JourneyEventsArray` tinytext ,
        `JourneyDuration` int DEFAULT NULL,
        `HitType` int DEFAULT NULL,
        `HitRate` decimal(10,7) DEFAULT NULL,
        `Velocity` decimal(10,7) DEFAULT NULL,
        `Momentum` decimal(10,7) DEFAULT NULL,
        `Closeness` decimal(10,7) DEFAULT NULL,
        `AdamicAdar` decimal(10,7) DEFAULT NULL,
        `Jaccard` decimal(10,7) DEFAULT NULL,
        `Louvain` decimal(10,7) DEFAULT NULL,
        `BFS` decimal(10,7) DEFAULT NULL,
        `MotivesArray` decimal(10,7) DEFAULT NULL,
        `CalabiYau` decimal(10,7) DEFAULT NULL,
        `Perspective` varchar(230)  NOT NULL DEFAULT '',
        `Transparency` decimal(10,7) DEFAULT NULL,
        `ABTesting` varchar(40)  NOT NULL DEFAULT '',
        `Neo4jParams` tinytext ,
        `OrderIdGraphUUID` BINARY(16),
        `OrderItemIdGraphUUID` BINARY(16),
        `PPIdGraphUUID` BINARY(16),
        `ProdIdGraphUUID` BINARY(16),
        `EpoIdGraphUUID` BINARY(16),
        `O_PP_GraphUUID` BINARY(16),
        `O_PP_Freq` INT,
        `O_Prod_GraphUUID` BINARY(16),
        `O_Prod_Freq` INT,
        `O_Epo_GraphUUID` BINARY(16),
        `O_Epo_Freq` INT,
        `PP_Epo_GraphUUID` BINARY(16),
        `PP_Epo_Freq` INT,
        `Prod_Epo_GraphUUID` BINARY(16),
        `Prod_Epo_Freq` INT,
        PRIMARY KEY (`Id`)
    ) {$charset_collate};";

  
  
  $wpdb->query($sql_table);

  
  $indexes = array(
    "aieo_idx_orderid_store_orders" => "(OrderId)",
    "aieo_idx_productid_store_orders" => "(ProductId)",
    "aieo_idx_parentproductid_store_orders" => "(ParentProductId)",
    "aieo_idx_brandid_store_orders" => "(BrandId)",
    "aieo_idx_eponymousid_store_orders" => "(EponymousID)",
    "aieo_idx_loyaltyid_store_orders" => "(LoyaltyID)"
  );

  
  foreach ($indexes as $index_name => $columns) {
    aieo_drop_index_if_exists($index_name, $temp_store_orders_table_name);
    $wpdb->query("CREATE INDEX {$index_name} ON {$temp_store_orders_table_name} {$columns};");
  }
}

function aieo_create_temp_products_table()
{
  global $wpdb;

  
  $temp_products_table_name = $wpdb->prefix . 'aieo_temp_product_catalogue';

  
  $charset_collate = aieo_get_charset_collate();

  $sql_table = "CREATE TABLE IF NOT EXISTS {$temp_products_table_name}  (
    `Id` int NOT NULL AUTO_INCREMENT,
    `OrderId` bigint unsigned NOT NULL,
    `OrderStatus` varchar(40)  DEFAULT NULL,
    `SKU` varchar(230)  DEFAULT NULL,
    `ParentSKU` varchar(200)   DEFAULT NULL,
    `ProductId` int DEFAULT NULL,
    `ParentProductId` int DEFAULT NULL,
    `CategoryId` varchar(230)  DEFAULT NULL,
    `TagId` varchar(230)  DEFAULT NULL,
    `OrderDate` datetime DEFAULT NULL,
    `PrimaryQty` int DEFAULT NULL,
    `ItemSequence` bigint unsigned NOT NULL DEFAULT '0',
    `Stock` int DEFAULT NULL,
    `OrderSize` bigint DEFAULT '0',
    `EngagementTime` int DEFAULT NULL,
    `JourneyLinearWeight` decimal(25,4) DEFAULT NULL,
    `AnonymousWeight` varchar(1)  NOT NULL DEFAULT '',
    `Price` decimal(10,2) DEFAULT NULL,
    `RegularPrice` decimal(10,2) DEFAULT NULL,
    `OnSale` int DEFAULT NULL,
    `RoleBasedSpecialPricing` int DEFAULT NULL,
    `Discontinued` int DEFAULT NULL,
    `HasReciprocal` int DEFAULT NULL,
    `LineValue` decimal(10,2) DEFAULT NULL,
    `OrderItemsTotal` decimal(10,2) DEFAULT NULL,
    `ValueLinearWeight` decimal(10,2) DEFAULT NULL,
    `CurrentPrice` decimal(10,2) DEFAULT NULL,
    `CurrentRegularPrice` decimal(10,2) DEFAULT NULL,
    `ItemName` longtext  NULL,
    `ParentItemName` longtext ,
    `ItemNameDiff` varchar(500)  DEFAULT NULL,
    `TotalItemSales` int DEFAULT NULL,
    `DistinctOrderSales` int DEFAULT NULL,
    `TotalTurnover` decimal(10,2) DEFAULT NULL,
    `UniqueItemSales` int DEFAULT NULL,
    `FirstItemSales` int DEFAULT NULL,
    `LastItemSales` int DEFAULT NULL,
    `AvgJLW` decimal(10,7) DEFAULT NULL,
    `AvgVLW` decimal(10,7) DEFAULT NULL,
    `ProfitabilityIndex` int DEFAULT NULL,
    `BrandId` int DEFAULT NULL,
    `Brand` varchar(255)  DEFAULT NULL,
    `BrandSlug` varchar(255)  DEFAULT NULL,    
    `Vendor` varchar(255)   DEFAULT NULL,
    `VendorId` int DEFAULT NULL,   
    `MenuId` INT DEFAULT NULL,
    `MenuCategory` varchar(255)   DEFAULT NULL,
    `HeadId` INT DEFAULT NULL,
    `HeadCategory` varchar(255)   DEFAULT NULL,
    `SubcatId` INT DEFAULT NULL,
    `SubCategory` varchar(255)   DEFAULT NULL,
    `Weight` decimal(18,4) DEFAULT NULL,
    `Length` decimal(18,4) DEFAULT NULL,
    `Width` decimal(18,4) DEFAULT NULL,
    `Height` decimal(18,4) DEFAULT NULL,
    `Size` varchar(255)   DEFAULT NULL,
    `Color` varchar(255)   DEFAULT NULL,
    `ImageUrl` longtext   DEFAULT NULL,
    `ImageGalleryUrl` longtext   DEFAULT NULL,
    `ProductUrl` varchar(255)   DEFAULT NULL,
    `ERPId` varchar(50)   DEFAULT NULL,        
    `BrandAvgJLW` decimal(10,7) DEFAULT NULL,
    `BrandAvgVLW` decimal(10,7) DEFAULT NULL,
    `BrandTotalTurnover` decimal(10,7) DEFAULT NULL,
    `ContentWordCount` int DEFAULT NULL,
    `ContentIntro` longtext ,
    `ContentIntroPlain` longtext ,
    `ContentSentimentArray` varchar(300)  DEFAULT NULL,
    `ContentViews` int DEFAULT NULL,
    `ContentCreated` datetime DEFAULT CURRENT_TIMESTAMP,
    `ContentLastUpdated` datetime DEFAULT CURRENT_TIMESTAMP,
    `ContentIncoming` int DEFAULT NULL,
    `ContentOutgoing` int DEFAULT NULL,
    `ContentCorIndex` decimal(10,7) DEFAULT NULL,
    `CategoryName` longtext ,
    `CategoryCorIndex` decimal(10,7) DEFAULT NULL,
    `TagName` longtext ,
    `TagCorIndex` decimal(10,7) DEFAULT NULL,
    `KeyVerb` varchar(50)  DEFAULT NULL,
    `KeySentimentArray` varchar(500)  DEFAULT NULL,
    `EponymousID` int DEFAULT NULL,
    `AvgOrderSize` decimal(10,2) DEFAULT NULL,
    `MaxOrderSize` int DEFAULT NULL,
    `MinOrderSize` int  DEFAULT NULL,
    `SDOrderSize` DECIMAL(10,7) DEFAULT NULL, 
    `zOrderSize` DECIMAL(10,7) DEFAULT NULL, 
    `AvgSpend` decimal(10,2) DEFAULT NULL,
    `MaxSpend` decimal(10,2) DEFAULT NULL,
    `MinSpend` decimal(10,2) DEFAULT NULL,
    `SDSpend` DECIMAL(10,7) DEFAULT NULL,
    `zSpend` DECIMAL(10,7) DEFAULT NULL,
    `TotalCustomerItems` int DEFAULT NULL,
    `TotalCustomerOrders` int DEFAULT NULL,
    `TotalCustomerTurnover` decimal(10,2) DEFAULT NULL,
    `Fav1Product` int DEFAULT NULL,
    `Fav2Product` int DEFAULT NULL,
    `Fav3Product` int DEFAULT NULL,
    `Fav1Brand` int DEFAULT NULL,
    `Fav2Brand` int DEFAULT NULL,
    `Fav3Brand` int DEFAULT NULL,
    `Fav1MainCat` int DEFAULT NULL,
    `Fav2MainCat` int DEFAULT NULL,
    `Fav3MainCat` int DEFAULT NULL,
    `Fav1HeadCat` int DEFAULT NULL,
    `Fav2HeadCat` int DEFAULT NULL,
    `Fav3HeadCat` int DEFAULT NULL,
    `Fav1SubCat` int DEFAULT NULL,
    `Fav2SubCat` int DEFAULT NULL,
    `Fav3SubCat` int DEFAULT NULL,
    `Fav1Size` varchar(50)  DEFAULT NULL,
    `Fav2Size` varchar(50)  DEFAULT NULL,
    `Fav3Size` varchar(50)  DEFAULT NULL,
    `Fav1Color` varchar(50)  DEFAULT NULL,
    `Fav2Color` varchar(50)  DEFAULT NULL,
    `Fav3Color` varchar(50)  DEFAULT NULL,
    `LastTransactionDate` datetime DEFAULT NULL,
    `LastTransactionLocation` varchar(80) DEFAULT NULL,
    `LastOrderSpend` decimal(10,2) DEFAULT NULL,
    `CurrentDBO` INT DEFAULT NULL,
    `AvgDBO` decimal(10,2) DEFAULT NULL,
    `MaxDBO` INT DEFAULT NULL,
    `MinDBO` INT DEFAULT NULL,
    `SDDBO` DECIMAL(10,7) DEFAULT NULL,  
    `zDBO` DECIMAL(10,7) DEFAULT NULL,
    `maDBO` DECIMAL(10,7) DEFAULT NULL,
    `Seasonality` varchar(1)  NOT NULL DEFAULT '',
    `PartnerId` int DEFAULT NULL,
    `OriginId` int DEFAULT NULL,
    `OriginType` varchar(50)  DEFAULT NULL,
    `EventId` int DEFAULT NULL,
    `EventType` varchar(50)  DEFAULT NULL,
    `EventLocation` varchar(255)  DEFAULT NULL,
    `AffiliateId` int DEFAULT NULL,
    `GASource` varchar(50)  DEFAULT NULL,
    `GAMedium` varchar(50)  DEFAULT NULL,
    `GACampaign` varchar(50)  DEFAULT NULL,
    `GAContent` varchar(50)  DEFAULT NULL,
    `GATerm` varchar(50)  DEFAULT NULL,
    `GABrand` varchar(50)  DEFAULT NULL,
    `CustomerDevice` varchar(100)  DEFAULT NULL,
    `CustomerSegment` varchar(100)  DEFAULT NULL,
    `CustomerFeedback` varchar(100)  DEFAULT NULL,
    `CustomerFeedforward` varchar(100)  DEFAULT NULL,
    `JourneyEventsArray` tinytext ,
    `JourneyDuration` int DEFAULT NULL,
    `HitType` int DEFAULT NULL,
    `HitRate` decimal(10,7) DEFAULT NULL,
    `Velocity` decimal(10,7) DEFAULT NULL,
    `Momentum` decimal(10,7) DEFAULT NULL,
    `Closeness` decimal(10,7) DEFAULT NULL,
    `AdamicAdar` decimal(10,7) DEFAULT NULL,
    `Jaccard` decimal(10,7) DEFAULT NULL,
    `Louvain` decimal(10,7) DEFAULT NULL,
    `BFS` decimal(10,7) DEFAULT NULL,
    `MotivesArray` decimal(10,7) DEFAULT NULL,
    `CalabiYau` decimal(10,7) DEFAULT NULL,
    `Perspective` varchar(230)  NOT NULL DEFAULT '',
    `Transparency` decimal(10,7) DEFAULT NULL,
    `ABTesting` varchar(40)  NOT NULL DEFAULT '',
    `Neo4jParams` tinytext ,
    `OrderIdGraphUUID` BINARY(16),            -- UUID for distinct OrderId (BINARY for 2-3x faster JOINs)
    `OrderItemIdGraphUUID` BINARY(16),        -- UUID for distinct OrderItemId
    `PPIdGraphUUID` BINARY(16),               -- UUID for distinct ParentProductId
    `ProdIdGraphUUID` BINARY(16),             -- UUID for distinct ProductId
    `EpoIdGraphUUID` BINARY(16),              -- UUID for distinct EponymousId
    `O_PP_GraphUUID` BINARY(16),              -- UUID for distinct relation between OrderId and ParentProductId
    `O_PP_Freq` INT,
    `O_Prod_GraphUUID` BINARY(16),            -- UUID for distinct relation between OrderId and ProductId
    `O_Prod_Freq` INT,
    `O_Epo_GraphUUID` BINARY(16),             -- UUID for distinct relation between OrderId and EponymousId
    `O_Epo_Freq` INT,
    `PP_Epo_GraphUUID` BINARY(16),            -- UUID for distinct relation between ParentProductId and EponymousId
    `PP_Epo_Freq` INT,
    `Prod_Epo_GraphUUID` BINARY(16),          -- UUID for distinct relation between ProductId and EponymousId
    `Prod_Epo_Freq` INT,
    PRIMARY KEY (`Id`)
   ) {$charset_collate};";

  
  $wpdb->query($sql_table);

  
  $indexes = array(
    "aieo_idx_productid_products" => "(ProductId)",
    "aieo_idx_parentproductid_products" => "(ParentProductId)",
    "aieo_idx_SKU_products" => "(SKU)",
    "aieo_idx_parentSKU_products" => "(ParentSKU)",
    "aieo_idx_brandid_products" => "(BrandId)"
  );

  
  foreach ($indexes as $index_name => $columns) {
    aieo_drop_index_if_exists($index_name, $temp_products_table_name);
    $wpdb->query("CREATE INDEX {$index_name} ON {$temp_products_table_name} {$columns};");
  }
}

function aieo_create_function_AIEO_StripHTML()
{
  global $wpdb;

  
  if (!aieo_can_create_stored_routines()) {
      aieo_debug_log('AIEO: Skipping AIEO_StripHTML function creation - binary logging enabled but insufficient privileges. The plugin will work without this function. To enable: set log_bin_trust_function_creators=1 or grant SUPER privilege.');
      return false;
  }

  
  aieo_drop_function_if_exists('AIEO_StripHTML');

  $sql = "CREATE FUNCTION AIEO_StripHTML (
    input LONGTEXT
)
RETURNS LONGTEXT

  DETERMINISTIC
  READS SQL DATA
BEGIN
    DECLARE start INT DEFAULT 0;
    DECLARE end INT DEFAULT 0;
    DECLARE length INT DEFAULT 0;
    loop_label: LOOP
        SET start = LOCATE('<', input);
        IF start = 0 THEN
            LEAVE loop_label;
        END IF;
        SET end = LOCATE('>', input, start);
        SET length = (end - start) + 1;
        SET input = INSERT(input, start, length, '');
    END LOOP loop_label;

    RETURN input;
END;
";

  $result = $wpdb->query($sql);
  if ($result === false) {
      aieo_debug_log('AIEO: Failed to create AIEO_StripHTML function - ' . $wpdb->last_error);
      return false;
  }
  return true;
}

function aieo_create_function_AIEO_CountWords()
{
  global $wpdb;

  
  if (!aieo_can_create_stored_routines()) {
      aieo_debug_log('AIEO: Skipping AIEO_CountWords function creation - binary logging enabled but insufficient privileges. The plugin will work without this function. To enable: set log_bin_trust_function_creators=1 or grant SUPER privilege.');
      return false;
  }

  
  aieo_drop_function_if_exists('AIEO_CountWords');

  $sql = "CREATE FUNCTION AIEO_CountWords(input_text TEXT) RETURNS INT
    DETERMINISTIC READS SQL DATA
    BEGIN
        DECLARE wordCount INT;

        -- Remove leading and trailing spaces
        SET input_text = TRIM(input_text);

        -- Count spaces and add 1 to get word count
        SET wordCount = LENGTH(input_text) - LENGTH(REPLACE(input_text, ' ', '')) + 1;

        -- If the text is empty, we should return 0
        IF LENGTH(input_text) = 0 THEN
            SET wordCount = 0;
        END IF;

        RETURN wordCount;
    END;
";

  $result = $wpdb->query($sql);
  if ($result === false) {
      aieo_debug_log('AIEO: Failed to create AIEO_CountWords function - ' . $wpdb->last_error);
      return false;
  }
  return true;
}

function aieo_create_function_AIEO_CountLinks()
{
  global $wpdb;

  
  if (!aieo_can_create_stored_routines()) {
      aieo_debug_log('AIEO: Skipping AIEO_CountLinks function creation - binary logging enabled but insufficient privileges. The plugin will work without this function. To enable: set log_bin_trust_function_creators=1 or grant SUPER privilege.');
      return false;
  }

  
  aieo_drop_function_if_exists('AIEO_CountLinks');

  $sql = "CREATE FUNCTION AIEO_CountLinks(input_text TEXT) RETURNS INT
    DETERMINISTIC READS SQL DATA
    BEGIN
        DECLARE linkCount INT;

        SET linkCount = (LENGTH(input_text) - LENGTH(REPLACE(input_text, '<a', ''))) / LENGTH('<a');

        RETURN linkCount;
    END;
";

  $result = $wpdb->query($sql);
  if ($result === false) {
      aieo_debug_log('AIEO: Failed to create AIEO_CountLinks function - ' . $wpdb->last_error);
      return false;
  }
  return true;
}

function aieo_create_sp_UpdateProductCentricStats($sp_prefix)
{
  global $wpdb;

  $sp_name = $sp_prefix . "AIEO_UpdateProductCentricStats";

  $create_sql = "CREATE PROCEDURE $sp_name(IN target_table_name VARCHAR(255), IN use_product_id TINYINT)
        BEGIN
          DECLARE column_id VARCHAR(255);
          SET column_id = IF(use_product_id = 1, 'ProductId', 'ParentProductId');
          
          -- Update TotalItemSales
          SET @sql = CONCAT('UPDATE ', target_table_name, ' AS t1 
          INNER JOIN (
              SELECT ', column_id, ', SUM(PrimaryQty) AS TotalItemSales
              FROM ', target_table_name, '
              WHERE OriginId <> 2 OR LoyaltyID IS NOT NULL 
              GROUP BY ', column_id, '
          ) AS t2 ON t1.', column_id, ' = t2.', column_id, '
          SET t1.TotalItemSales = t2.TotalItemSales;
          ');

          PREPARE stmt FROM @sql;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;
          
          -- Update DistinctOrderSales
          SET @sql = CONCAT('
              UPDATE ', target_table_name, ' AS t1
              INNER JOIN (
                  SELECT ', column_id, ', COUNT(DISTINCT OrderId) AS DistinctOrderSales
                  FROM ', target_table_name, '
                  WHERE OriginId <> 2 OR LoyaltyID IS NOT NULL 
                  GROUP BY ', column_id, '
              ) AS t2 ON t1.', column_id, ' = t2.', column_id, '
              SET t1.DistinctOrderSales = t2.DistinctOrderSales;
          ');
          PREPARE stmt FROM @sql;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;

          -- Update TotalTurnover
          SET @sql = CONCAT('
              UPDATE ', target_table_name, ' AS t1
              INNER JOIN (
                  SELECT ', column_id, ', SUM(PrimaryQty * Price) AS TotalTurnover
                  FROM ', target_table_name, '
                  WHERE OriginId <> 2 OR LoyaltyID IS NOT NULL 
                  GROUP BY ', column_id, '
              ) AS t2 ON t1.', column_id, ' = t2.', column_id, '
              SET t1.TotalTurnover = t2.TotalTurnover;
          ');
          PREPARE stmt FROM @sql;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;

          -- Update UniqueItemSales with ItemSequence = 1 AND OrderSize = 1
          SET @sql = CONCAT('
              UPDATE ', target_table_name, ' AS t1
              INNER JOIN (
                  SELECT ', column_id, ', COUNT(DISTINCT OrderId) AS UniqueItemSales
                  FROM ', target_table_name, '
                  WHERE ItemSequence = 1 AND OrderSize = 1 AND (OriginId <> 2 OR LoyaltyID IS NOT NULL) 
                  GROUP BY ', column_id, '
              ) AS t2 ON t1.', column_id, ' = t2.', column_id, '
              SET t1.UniqueItemSales = t2.UniqueItemSales;
          ');
          PREPARE stmt FROM @sql;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;

          -- Update FirstItemSales with ItemSequence = 1
          SET @sql = CONCAT('
              UPDATE ', target_table_name, ' AS t1
              INNER JOIN (
                  SELECT ', column_id, ', COUNT(DISTINCT OrderId) AS FirstItemSales
                  FROM ', target_table_name, '
                  WHERE ItemSequence = 1 AND (OriginId <> 2 OR LoyaltyID IS NOT NULL) 
                  GROUP BY ', column_id, '
              ) AS t2 ON t1.', column_id, ' = t2.', column_id, '
              SET t1.FirstItemSales = t2.FirstItemSales;
          ');
          PREPARE stmt FROM @sql;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;

          -- Update LastItemSales with ItemSequence = OrderSize
          SET @sql = CONCAT('
              UPDATE ', target_table_name, ' AS t1
              INNER JOIN (
                  SELECT ', column_id, ', COUNT(DISTINCT OrderId) AS LastItemSales
                  FROM ', target_table_name, '
                  WHERE ItemSequence = OrderSize AND (OriginId <> 2 OR LoyaltyID IS NOT NULL) 
                  GROUP BY ', column_id, '
              ) AS t2 ON t1.', column_id, ' = t2.', column_id, '
              SET t1.LastItemSales = t2.LastItemSales;
          ');
          PREPARE stmt FROM @sql;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;

          -- Update AvgJLW
          SET @sql = CONCAT('
              UPDATE ', target_table_name, ' AS t1
              INNER JOIN (
                  SELECT ', column_id, ', AVG(JourneyLinearWeight) AS AvgJLWValue
                  FROM ', target_table_name, '
                  WHERE OriginId <> 2 OR LoyaltyID IS NOT NULL 
                  GROUP BY ', column_id, '
              ) AS t2 ON t1.', column_id, ' = t2.', column_id, '
              SET t1.AvgJLW = t2.AvgJLWValue;
          ');
          PREPARE stmt FROM @sql;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;

          -- Update AvgVLW
          SET @sql = CONCAT('
              UPDATE ', target_table_name, ' AS t1
              INNER JOIN (
                  SELECT ', column_id, ', AVG(ValueLinearWeight) AS AvgVLWValue
                  FROM ', target_table_name, '
                  WHERE OriginId <> 2 OR LoyaltyID IS NOT NULL 
                  GROUP BY ', column_id, '
              ) AS t2 ON t1.', column_id, ' = t2.', column_id, '
              SET t1.AvgVLW = t2.AvgVLWValue;
          ');
          PREPARE stmt FROM @sql;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;

          PREPARE stmt FROM @sql;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;

      END;
";

  $wpdb->query($create_sql);
}

function aieo_create_sp_AIEO_GenerateGraphDBUUIDFreqs($sp_prefix)
{
  global $wpdb;

  $procedure_name = "{$sp_prefix}AIEO_GenerateGraphDBUUIDFreqs";

  $sql = "CREATE PROCEDURE {$procedure_name}(IN target_table_name VARCHAR(255))
  BEGIN
-- O_PP_Freq based on O_PP_GraphUUID:

SET @sql37 = CONCAT('UPDATE ', target_table_name, ' t1 
    JOIN (
        SELECT Prod_Epo_GraphUUID, COUNT(*) as FreqCount
        FROM ', target_table_name, '
        GROUP BY Prod_Epo_GraphUUID
    ) t2 ON t1.Prod_Epo_GraphUUID = t2.Prod_Epo_GraphUUID
    SET t1.Prod_Epo_Freq = t2.FreqCount;');
PREPARE stmt37 FROM @sql37;
EXECUTE stmt37;
DEALLOCATE PREPARE stmt37;

-- O_PP_Freq based on O_PP_GraphUUID:
SET @sql38 = CONCAT('UPDATE ', target_table_name, ' t1 
    JOIN (
        SELECT O_PP_GraphUUID, COUNT(*) as FreqCount
        FROM ', target_table_name, '
        GROUP BY O_PP_GraphUUID
    ) t2 ON t1.O_PP_GraphUUID = t2.O_PP_GraphUUID
    SET t1.O_PP_Freq = t2.FreqCount;');
PREPARE stmt38 FROM @sql38;
EXECUTE stmt38;
DEALLOCATE PREPARE stmt38;

-- O_Prod_Freq based on O_Prod_GraphUUID:
SET @sql39 = CONCAT('UPDATE ', target_table_name, ' t1 
    JOIN (
        SELECT O_Prod_GraphUUID, COUNT(*) as FreqCount
        FROM ', target_table_name, '
        GROUP BY O_Prod_GraphUUID
    ) t2 ON t1.O_Prod_GraphUUID = t2.O_Prod_GraphUUID
    SET t1.O_Prod_Freq = t2.FreqCount;');
PREPARE stmt39 FROM @sql39;
EXECUTE stmt39;
DEALLOCATE PREPARE stmt39;

-- O_Epo_Freq based on O_Epo_GraphUUID:
SET @sql40 = CONCAT('UPDATE ', target_table_name, ' t1 
    JOIN (
        SELECT O_Epo_GraphUUID, COUNT(*) as FreqCount
        FROM ', target_table_name, '
        GROUP BY O_Epo_GraphUUID
    ) t2 ON t1.O_Epo_GraphUUID = t2.O_Epo_GraphUUID
    SET t1.O_Epo_Freq = t2.FreqCount;');
PREPARE stmt40 FROM @sql40;
EXECUTE stmt40;
DEALLOCATE PREPARE stmt40;

-- PP_Epo_Freq based on PP_Epo_GraphUUID:
SET @sql41 = CONCAT('UPDATE ', target_table_name, ' t1 
    JOIN (
        SELECT PP_Epo_GraphUUID, COUNT(*) as FreqCount
        FROM ', target_table_name, '
        GROUP BY PP_Epo_GraphUUID
    ) t2 ON t1.PP_Epo_GraphUUID = t2.PP_Epo_GraphUUID
    SET t1.PP_Epo_Freq = t2.FreqCount;');
PREPARE stmt41 FROM @sql41;
EXECUTE stmt41;
DEALLOCATE PREPARE stmt41;

SET @sql39 = CONCAT('UPDATE ', target_table_name, ' t1 
    JOIN (
        SELECT O_Prod_GraphUUID, COUNT(*) as FreqCount
        FROM ', target_table_name, '
        GROUP BY O_Prod_GraphUUID
    ) t2 ON t1.O_Prod_GraphUUID = t2.O_Prod_GraphUUID
    SET t1.O_Prod_Freq = t2.FreqCount;');
PREPARE stmt39 FROM @sql39;
EXECUTE stmt39;
DEALLOCATE PREPARE stmt39;

  END;

";

  
  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_GenerateGraphDBUUIDs($sp_prefix)
{
  global $wpdb;

  $procedure_name = "{$sp_prefix}AIEO_GenerateGraphDBUUIDs";

  $sql = "CREATE PROCEDURE {$procedure_name}(IN target_table_name VARCHAR(255))
  BEGIN

  -- For EponymousId (EpoIdGraphUUID)
  SET @sql13 = CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS tempUUIDs13 (
            originalValue BIGINT,
            generatedUUID BINARY(16),
            INDEX idx_original (originalValue)
        ) ENGINE=InnoDB;');
        PREPARE stmt13 FROM @sql13;
        EXECUTE stmt13;
        DEALLOCATE PREPARE stmt13;

        SET @sql14 = CONCAT('INSERT INTO tempUUIDs13 (originalValue, generatedUUID)
            SELECT DISTINCT EponymousId, UNHEX(REPLACE(UUID(), \'-\', \'\')) 
            FROM ', target_table_name, ';');
        PREPARE stmt14 FROM @sql14;
        EXECUTE stmt14;
        DEALLOCATE PREPARE stmt14;

        SET @sql15 = CONCAT('UPDATE ', target_table_name, ' 
            JOIN tempUUIDs13 ON ', target_table_name, '.EponymousId = tempUUIDs13.originalValue 
            SET ', target_table_name, '.EpoIdGraphUUID = tempUUIDs13.generatedUUID;');
        PREPARE stmt15 FROM @sql15;
        EXECUTE stmt15;
        DEALLOCATE PREPARE stmt15;

        SET @sql16 = 'DROP TEMPORARY TABLE tempUUIDs13;';
        PREPARE stmt16 FROM @sql16;
        EXECUTE stmt16;
        DEALLOCATE PREPARE stmt16;

        
-- For O_Epo_GraphUUID (OrderId and EponymousId)
SET @sql25 = CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS tempUUIDs25 (
    OrderIdValue BIGINT,
    EponymousIdValue BIGINT,
    generatedUUID BINARY(16),
    INDEX idx_composite (OrderIdValue, EponymousIdValue)
) ENGINE=InnoDB;');
PREPARE stmt25 FROM @sql25;
EXECUTE stmt25;
DEALLOCATE PREPARE stmt25;

SET @sql26 = CONCAT('INSERT INTO tempUUIDs25 (OrderIdValue, EponymousIdValue, generatedUUID)
    SELECT DISTINCT OrderId, EponymousId, UNHEX(REPLACE(UUID(), \'-\', \'\')) 
    FROM ', target_table_name, ' 
    WHERE EponymousId <> 0 AND EponymousId IS NOT NULL;');
PREPARE stmt26 FROM @sql26;
EXECUTE stmt26;
DEALLOCATE PREPARE stmt26;

SET @sql27 = CONCAT('UPDATE ', target_table_name, ' 
    JOIN tempUUIDs25 ON ', target_table_name, '.OrderId = tempUUIDs25.OrderIdValue AND ', target_table_name, '.EponymousId = tempUUIDs25.EponymousIdValue
    SET ', target_table_name, '.O_Epo_GraphUUID = tempUUIDs25.generatedUUID;');
PREPARE stmt27 FROM @sql27;
EXECUTE stmt27;
DEALLOCATE PREPARE stmt27;

SET @sql28 = 'DROP TEMPORARY TABLE tempUUIDs25;';
PREPARE stmt28 FROM @sql28;
EXECUTE stmt28;
DEALLOCATE PREPARE stmt28;

-- For PP_Epo_GraphUUID (ParentProductId and EponymousId)
SET @sql29 = CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS tempUUIDs29 (
    ParentProductIdValue BIGINT,
    EponymousIdValue BIGINT,
    generatedUUID BINARY(16)
) ENGINE=InnoDB;');
PREPARE stmt29 FROM @sql29;
EXECUTE stmt29;
DEALLOCATE PREPARE stmt29;

SET @sql30 = CONCAT('INSERT INTO tempUUIDs29 (ParentProductIdValue, EponymousIdValue, generatedUUID)
    SELECT DISTINCT ParentProductId, EponymousId, UNHEX(REPLACE(UUID(), \'-\', \'\')) 
    FROM ', target_table_name, ' 
    WHERE EponymousId <> 0 AND EponymousId IS NOT NULL;');
PREPARE stmt30 FROM @sql30;
EXECUTE stmt30;
DEALLOCATE PREPARE stmt30;

SET @sql31 = CONCAT('UPDATE ', target_table_name, ' 
    JOIN tempUUIDs29 ON ', target_table_name, '.ParentProductId = tempUUIDs29.ParentProductIdValue AND ', target_table_name, '.EponymousId = tempUUIDs29.EponymousIdValue
    SET ', target_table_name, '.PP_Epo_GraphUUID = tempUUIDs29.generatedUUID;');
PREPARE stmt31 FROM @sql31;
EXECUTE stmt31;
DEALLOCATE PREPARE stmt31;

SET @sql32 = 'DROP TEMPORARY TABLE tempUUIDs29;';
PREPARE stmt32 FROM @sql32;
EXECUTE stmt32;
DEALLOCATE PREPARE stmt32;

-- For Prod_Epo_GraphUUID (ProductId and EponymousId)
SET @sql33 = CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS tempUUIDs33 (
    ProductIdValue BIGINT,
    EponymousIdValue BIGINT,
    generatedUUID BINARY(16)
) ENGINE=InnoDB;');
PREPARE stmt33 FROM @sql33;
EXECUTE stmt33;
DEALLOCATE PREPARE stmt33;

SET @sql34 = CONCAT('INSERT INTO tempUUIDs33 (ProductIdValue, EponymousIdValue, generatedUUID)
    SELECT DISTINCT ProductId, EponymousId, UNHEX(REPLACE(UUID(), \'-\', \'\')) 
    FROM ', target_table_name, ' 
    WHERE EponymousId <> 0 AND EponymousId IS NOT NULL;');
PREPARE stmt34 FROM @sql34;
EXECUTE stmt34;
DEALLOCATE PREPARE stmt34;

SET @sql35 = CONCAT('UPDATE ', target_table_name, ' 
    JOIN tempUUIDs33 ON ', target_table_name, '.ProductId = tempUUIDs33.ProductIdValue AND ', target_table_name, '.EponymousId = tempUUIDs33.EponymousIdValue
    SET ', target_table_name, '.Prod_Epo_GraphUUID = tempUUIDs33.generatedUUID;');
PREPARE stmt35 FROM @sql35;
EXECUTE stmt35;
DEALLOCATE PREPARE stmt35;

SET @sql36 = 'DROP TEMPORARY TABLE tempUUIDs33;';
PREPARE stmt36 FROM @sql36;
EXECUTE stmt36;
DEALLOCATE PREPARE stmt36;
      

      -- For ParentProductId
      -- First set of operations

      SET @sql1 = CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS tempUUIDs1 (
          originalValue BIGINT,
          generatedUUID BINARY(16)
      ) ENGINE=InnoDB;');
      PREPARE stmt1 FROM @sql1;
      EXECUTE stmt1;
      DEALLOCATE PREPARE stmt1;

      SET @sql2 = CONCAT('INSERT INTO tempUUIDs1 (originalValue, generatedUUID)
          SELECT DISTINCT ParentProductId, UNHEX(REPLACE(UUID(), \'-\', \'\')) 
          FROM ', target_table_name, ';');
      PREPARE stmt2 FROM @sql2;
      EXECUTE stmt2;
      DEALLOCATE PREPARE stmt2;

      SET @sql3 = CONCAT('UPDATE ', target_table_name, ' 
          JOIN tempUUIDs1 ON ', target_table_name, '.ParentProductId = tempUUIDs1.originalValue 
          SET ', target_table_name, '.PPIdGraphUUID = tempUUIDs1.generatedUUID;');
      PREPARE stmt3 FROM @sql3;
      EXECUTE stmt3;
      DEALLOCATE PREPARE stmt3;

      SET @sql4 = 'DROP TEMPORARY TABLE tempUUIDs1;';
      PREPARE stmt4 FROM @sql4;
      EXECUTE stmt4;
      DEALLOCATE PREPARE stmt4;

      -- For OrderId
      SET @sql5 = CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS tempUUIDs5 (
          originalValue BIGINT,
          generatedUUID BINARY(16)
      ) ENGINE=InnoDB;');
      PREPARE stmt5 FROM @sql5;
      EXECUTE stmt5;
      DEALLOCATE PREPARE stmt5;

      SET @sql6 = CONCAT('INSERT INTO tempUUIDs5 (originalValue, generatedUUID)
          SELECT DISTINCT OrderId, UNHEX(REPLACE(UUID(), \'-\', \'\')) 
          FROM ', target_table_name, ';');
      PREPARE stmt6 FROM @sql6;
      EXECUTE stmt6;
      DEALLOCATE PREPARE stmt6;

      SET @sql7 = CONCAT('UPDATE ', target_table_name, ' 
          JOIN tempUUIDs5 ON ', target_table_name, '.OrderId = tempUUIDs5.originalValue 
          SET ', target_table_name, '.OrderIdGraphUUID = tempUUIDs5.generatedUUID;');
      PREPARE stmt7 FROM @sql7;
      EXECUTE stmt7;
      DEALLOCATE PREPARE stmt7;

      SET @sql8 = 'DROP TEMPORARY TABLE tempUUIDs5;';
      PREPARE stmt8 FROM @sql8;
      EXECUTE stmt8;
      DEALLOCATE PREPARE stmt8;

        -- For ProductId (ProdIdGraphUUID)
        SET @sql9 = CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS tempUUIDs19 (
            originalValue BIGINT,
            generatedUUID BINARY(16)
        ) ENGINE=InnoDB;');
        PREPARE stmt9 FROM @sql9;
        EXECUTE stmt9;
        DEALLOCATE PREPARE stmt9;

        SET @sql10 = CONCAT('INSERT INTO tempUUIDs19 (originalValue, generatedUUID)
            SELECT DISTINCT ProductId, UNHEX(REPLACE(UUID(), \'-\', \'\')) 
            FROM ', target_table_name, ';');
        PREPARE stmt10 FROM @sql10;
        EXECUTE stmt10;
        DEALLOCATE PREPARE stmt10;

        SET @sql11 = CONCAT('UPDATE ', target_table_name, ' 
            JOIN tempUUIDs19 ON ', target_table_name, '.ProductId = tempUUIDs19.originalValue 
            SET ', target_table_name, '.ProdIdGraphUUID = tempUUIDs19.generatedUUID;');
        PREPARE stmt11 FROM @sql11;
        EXECUTE stmt11;
        DEALLOCATE PREPARE stmt11;

        SET @sql12 = 'DROP TEMPORARY TABLE tempUUIDs19;';
        PREPARE stmt12 FROM @sql12;
        EXECUTE stmt12;
        DEALLOCATE PREPARE stmt12;

        
-- For O_PP_GraphUUID (OrderId and ParentProductId)
SET @sql17 = CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS tempUUIDs17 (
            OrderIdValue BIGINT,
            ParentProductIdValue BIGINT,
            generatedUUID BINARY(16)
        ) ENGINE=InnoDB;');
        PREPARE stmt17 FROM @sql17;
        EXECUTE stmt17;
        DEALLOCATE PREPARE stmt17;

        SET @sql18 = CONCAT('INSERT INTO tempUUIDs17 (OrderIdValue, ParentProductIdValue, generatedUUID)
            SELECT DISTINCT OrderId, ParentProductId, UNHEX(REPLACE(UUID(), \'-\', \'\')) 
            FROM ', target_table_name, ';');
        PREPARE stmt18 FROM @sql18;
        EXECUTE stmt18;
        DEALLOCATE PREPARE stmt18;

        SET @sql19 = CONCAT('UPDATE ', target_table_name, ' 
            JOIN tempUUIDs17 ON ', target_table_name, '.OrderId = tempUUIDs17.OrderIdValue AND ', target_table_name, '.ParentProductId = tempUUIDs17.ParentProductIdValue
            SET ', target_table_name, '.O_PP_GraphUUID = tempUUIDs17.generatedUUID;');
        PREPARE stmt19 FROM @sql19;
        EXECUTE stmt19;
        DEALLOCATE PREPARE stmt19;

        SET @sql20 = 'DROP TEMPORARY TABLE tempUUIDs17;';
        PREPARE stmt20 FROM @sql20;
        EXECUTE stmt20;
        DEALLOCATE PREPARE stmt20;

-- For O_Prod_GraphUUID (OrderId and ProductId)
SET @sql21 = CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS tempUUIDs_O_Prod (
    OrderIdValue BIGINT,
    ProductIdValue BIGINT,
    generatedUUID BINARY(16)
) ENGINE=InnoDB;');
PREPARE stmt21 FROM @sql21;
EXECUTE stmt21;
DEALLOCATE PREPARE stmt21;

SET @sql22 = CONCAT('INSERT INTO tempUUIDs_O_Prod (OrderIdValue, ProductIdValue, generatedUUID)
    SELECT DISTINCT OrderId, ProductId, UNHEX(REPLACE(UUID(), \'-\', \'\')) 
    FROM ', target_table_name, ';');
PREPARE stmt22 FROM @sql22;
EXECUTE stmt22;
DEALLOCATE PREPARE stmt22;

SET @sql23 = CONCAT('UPDATE ', target_table_name, ' 
    JOIN tempUUIDs_O_Prod ON ', target_table_name, '.OrderId = tempUUIDs_O_Prod.OrderIdValue AND ', target_table_name, '.ProductId = tempUUIDs_O_Prod.ProductIdValue
    SET ', target_table_name, '.O_Prod_GraphUUID = tempUUIDs_O_Prod.generatedUUID;');
PREPARE stmt23 FROM @sql23;
EXECUTE stmt23;
DEALLOCATE PREPARE stmt23;

END";

  
  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_UpdateOrdersWithUnsold($sp_prefix, $sp_orders_table, $sp_products_table)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_UpdateOrdersWithUnsold(OUT warning_message VARCHAR(255))
proc_label: BEGIN

  -- Check if the orders table is empty
  IF (SELECT COUNT(*) FROM $sp_orders_table) = 0 THEN
      SET warning_message = CONCAT('Warning: ', '$sp_orders_table', ' is empty. The operation cannot proceed.');
      LEAVE proc_label;
  END IF;

  START TRANSACTION;

  -- INSERT Operation
  INSERT INTO $sp_orders_table 
  (OrderStatus, SKU, ProductId, ParentProductId, CategoryId, TagId, CurrentPrice, CurrentRegularPrice, Stock, ItemName, ParentItemName, Brand, CategoryName, TagName, ContentIntro, ContentIntroPlain, ContentCreated, ContentLastUpdated)
  SELECT 
  st.OrderStatus, st.SKU, st.ProductId, st.ParentProductId, st.CategoryId, st.TagId, 
  st.CurrentPrice, st.CurrentRegularPrice, st.Stock, st.ItemName, st.ParentItemName, 
  st.Brand, st.CategoryName, st.TagName, st.ContentIntro, st.ContentIntroPlain, 
  st.ContentCreated, st.ContentLastUpdated
  FROM $sp_products_table st
  LEFT JOIN $sp_orders_table atot ON atot.ProductId = st.ProductId
  WHERE atot.ProductId IS NULL;

  -- UPDATE Operation
  UPDATE $sp_orders_table atot
  JOIN $sp_products_table st
  ON atot.ProductId = st.ProductId
  SET 
  atot.PPIdGraphUUID = st.PPIdGraphUUID,
  atot.ProdIdGraphUUID = st.ProdIdGraphUUID;

  COMMIT;

  -- Set the success message
  SET warning_message = ', and included the products that have no sales';

END;";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_StripHTMLAndReplicateContentIntro($sp_prefix, $sp_orders_table)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_StripHTMLAndReplicateContentIntro()
  BEGIN
  
    -- Declare variables
    DECLARE v_done INT DEFAULT 0;
    DECLARE v_ProductId INT;
    DECLARE v_ContentIntro TEXT;
  
    -- Declare cursor for unique ProductIds
    DECLARE cur_ProductIds CURSOR FOR 
        SELECT DISTINCT ProductId 
        FROM $sp_orders_table; 
  
    -- Declare handlers
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
  
    -- Open the cursor
    OPEN cur_ProductIds;
  
    -- Start the loop
    read_loop: LOOP
    
        FETCH cur_ProductIds INTO v_ProductId;
  
        IF v_done THEN
            LEAVE read_loop;
        END IF;
  
        -- Start a transaction
        START TRANSACTION;
  
        -- Update ContentIntro for the first occurrence based on OrderId for the current ProductId
        UPDATE $sp_orders_table 
        SET ContentIntro = AIEO_StripHTML(ContentIntro)
        WHERE ProductId = v_ProductId
        ORDER BY OrderId ASC
        LIMIT 1;
  
        -- Fetch the updated value
        SELECT ContentIntro INTO v_ContentIntro
        FROM $sp_orders_table
        WHERE ProductId = v_ProductId
        LIMIT 1;
  
        -- Copy the value to other instances with different OrderId
        UPDATE $sp_orders_table
        SET ContentIntro = v_ContentIntro
        WHERE ProductId = v_ProductId;
  
        -- Commit the transaction
        COMMIT;
  
    END LOOP;
  
    -- Close the cursor
    CLOSE cur_ProductIds;
  
  END;
";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_RenameTable($sp_prefix)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_RenameTable(IN oldTableName VARCHAR(255), IN newTableName VARCHAR(255))
  BEGIN
        -- Start transaction
        START TRANSACTION;
    
        -- Drop the table with new name if it already exists
        SET @drop_query = CONCAT('DROP TABLE IF EXISTS ', newTableName);
        PREPARE drop_stmt FROM @drop_query;
        EXECUTE drop_stmt;
        DEALLOCATE PREPARE drop_stmt;
    
        -- Rename the old table to the new table name
        SET @rename_query = CONCAT('RENAME TABLE ', oldTableName , ' TO ', newTableName);
        PREPARE rename_stmt FROM @rename_query;
        EXECUTE rename_stmt;
        DEALLOCATE PREPARE rename_stmt;
    
        -- Commit the transaction to finalize changes
        COMMIT;
  END;
  ";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_UpdateContentIntroPlain($sp_prefix)
{
  global $wpdb;

  $sql = "
    CREATE PROCEDURE " . $sp_prefix . "AIEO_UpdateContentIntroPlain (IN tableName VARCHAR(255))
    BEGIN
        -- Single set-based UPDATE. The previous implementation cursored over
        -- every (ParentProductId, ContentIntro) pair and CONCAT'd the cleaned
        -- TEXT into a fresh dynamic UPDATE per row: any apostrophe in product
        -- copy broke the spliced statement (a certainty on Greek-language
        -- catalogs), NULL intros made @updateText NULL and failed the PREPARE,
        -- and splicing arbitrary product text into SQL is stored-SQLi-shaped.
        -- AIEO_StripHTMLAndReplicateContentIntro has already replicated
        -- ContentIntro uniformly across each product's rows by the time this
        -- runs, so the row-wise strip below is semantically identical — one
        -- pass, no temp table, no cursor, and product text never enters the
        -- SQL string (only the internal table NAME stays dynamic).
        SET @sqlText = CONCAT('UPDATE ', tableName, ' SET ContentIntroPlain = AIEO_StripHTML(ContentIntro)');
        PREPARE stmt FROM @sqlText;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
    END;
    ";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_UpdateContentWordCount($sp_prefix)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_UpdateContentWordCount(
      IN _table_name VARCHAR(255))
    BEGIN
        SET @query = CONCAT('UPDATE ', _table_name, ' SET ContentWordCount = AIEO_CountWords(ContentIntroPlain)');
        PREPARE stmt FROM @query;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
    END;
    ";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_UpdateContentOutgoingLinks($sp_prefix)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_UpdateContentOutgoingLinks (
        IN _table_name VARCHAR(255))
      BEGIN
          SET @query = CONCAT('UPDATE ', _table_name, ' SET ContentOutgoing = AIEO_CountLinks(ContentIntro)');
          PREPARE stmt FROM @query;
          EXECUTE stmt;
          DEALLOCATE PREPARE stmt;
      END;
      ";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_FindDifferences($sp_prefix)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_FindDifferences(IN in_table_name VARCHAR(255))
    DETERMINISTIC READS SQL DATA
    BEGIN
        -- Variables declaration
        DECLARE v_sql TEXT;
        DECLARE v_diff_part TEXT DEFAULT '';
        DECLARE v_word_count_parent INT DEFAULT 0;
        DECLARE v_word_count_item INT DEFAULT 0;
        DECLARE v_iter INT DEFAULT 1;
    
        -- Calculate word count of the ParentItemName for one record as an example.
        SET @v_sql = CONCAT('SELECT ROUND((CHAR_LENGTH(ParentItemName) - CHAR_LENGTH(REPLACE(ParentItemName, '' '', ''''))) / CHAR_LENGTH('' '') + 1) INTO @v_word_count_temp FROM ', in_table_name, ' LIMIT 1');
        PREPARE stmt1 FROM @v_sql;
        EXECUTE stmt1;
        DEALLOCATE PREPARE stmt1;
    
        -- Calculate word count of the ItemName for the same record.
        SET @v_sql = CONCAT('SELECT ROUND((CHAR_LENGTH(ItemName) - CHAR_LENGTH(REPLACE(ItemName, '' '', ''''))) / CHAR_LENGTH('' '') + 1) INTO @v_word_count_temp_item FROM ', in_table_name, ' LIMIT 1');
        PREPARE stmt2 FROM @v_sql;
        EXECUTE stmt2;
        DEALLOCATE PREPARE stmt2;
    
        SET v_word_count_parent = @v_word_count_temp;
        SET v_word_count_item = @v_word_count_temp_item;
    
        -- Construct the column of differing words from ParentItemName not in ItemName
        WHILE v_iter <= v_word_count_parent DO
            SET v_diff_part = CONCAT(v_diff_part, 'IF(ItemName NOT LIKE CONCAT(''%'', SUBSTRING_INDEX(SUBSTRING_INDEX(ParentItemName, '' '', ', v_iter, '), '' '', -1), ''%''), CONCAT(SUBSTRING_INDEX(SUBSTRING_INDEX(ParentItemName, '' '', ', v_iter, '), '' '', -1), '' ''), '''')', ',');
            SET v_iter = v_iter + 1;
        END WHILE;
    
        SET v_iter = 1;
    
        -- Construct the column of differing words from ItemName not in ParentItemName
        WHILE v_iter <= v_word_count_item DO
            SET v_diff_part = CONCAT(v_diff_part, 'IF(ParentItemName NOT LIKE CONCAT(''%'', SUBSTRING_INDEX(SUBSTRING_INDEX(ItemName, '' '', ', v_iter, '), '' '', -1), ''%''), CONCAT(SUBSTRING_INDEX(SUBSTRING_INDEX(ItemName, '' '', ', v_iter, '), '' '', -1), '' ''), '''')', ',');
            SET v_iter = v_iter + 1;
        END WHILE;
    
        -- Trim the last comma
        SET v_diff_part = LEFT(v_diff_part, CHAR_LENGTH(v_diff_part) - 1);
    
        -- Begin the construction of the dynamic SQL statement to update ItemNameDiff
        SET @v_sql = CONCAT('UPDATE ', in_table_name, ' SET ItemNameDiff = REPLACE(TRIM(CONCAT_WS('' '', ', v_diff_part, ')), ''  '', '' '')');
    
        -- Execute the dynamic SQL
        PREPARE dynamic_statement FROM @v_sql;
        EXECUTE dynamic_statement;
        DEALLOCATE PREPARE dynamic_statement;
    END;
    ";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_Create_Product_Catalogue_HPOS($sp_prefix, $sp_products_table)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_Create_Product_Catalogue_HPOS(
    IN _price_sql_choice INT, 
    IN _text_sql_choice INT,
    IN _full_text_sql_choice INT
     
  ) BEGIN 
  TRUNCATE TABLE $sp_products_table;
  INSERT INTO $sp_products_table (OrderId, OrderStatus, SKU, ParentSKU, ProductId, ParentProductId, CategoryId, TagId, Stock, OnSale, RoleBasedSpecialPricing, CurrentPrice, CurrentRegularPrice, ItemName, ParentItemName, ProfitabilityIndex, VendorId, Vendor, BrandId, Brand, BrandSlug, MenuId, 
  MenuCategory, HeadId, HeadCategory, SubcatId, SubCategory, Weight, Length, Width, Height, Size, Color, ImageUrl, ImageGalleryUrl, ContentIntro, ContentIntroPlain, ProductUrl, ERPId, CategoryName, TagName, Seasonality, ContentCreated, ContentLastUpdated, ProdIdGraphUUID, PPIdGraphUUID)
  SELECT 
      0 AS OrderId, 
      'unsold' AS OrderStatus,
      pv.sku AS SKU,
      pv.parent_sku AS ParentSKU,
      pv.product_id AS ProductId, 
      pv.parent_product_id AS ParentProductId, 
      pv.category_ids AS CategoryId, 
      pv.tag_ids AS TagId, 
      pv.stock AS Stock,
      pv.onsale AS OnSale,
      pv.role_special_pricing AS RoleBasedSpecialPricing,     
      (CASE 
        WHEN _price_sql_choice = 0 THEN '' 
        ELSE pv.price END
      ) AS CurrentPrice,
      (CASE 
      WHEN _price_sql_choice = 0 THEN '' 
      ELSE pv.regular_price END
      ) AS CurrentRegularPrice,
      (
        CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.product_name END
        ) AS ItemName, 
      (
        CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.parent_product_name END
        ) AS ParentItemName, 
      '1' AS ProfitabilityIndex, 
      pv.vendor_id AS VendorId,
      (CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.vendor END
      )   AS Vendor,
      pv.brand_id AS BrandId,
      (CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.brand END
      )   AS Brand, 
      (CASE 
      WHEN _text_sql_choice = 0 THEN '' 
      ELSE pv.brand_slug END) AS BrandSlug,   
      pv.menu_id AS MenuId,
      (CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.menu_category END
        ) AS MenuCategory,
      pv.head_id AS HeadId,
      ( CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.head_category END
        ) AS HeadCategory,
      pv.subcat_id AS SubcatId,
      (
        CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.sub_category END
        ) AS SubCategory, 
      pv.weight AS Weight,
      pv.length AS Length,
      pv.width AS Width,
      pv.height AS Height,
      pv.size AS Size,
      pv.color AS Color, 
      pv.image_url AS ImageUrl, 
      pv.image_gallery_urls AS ImageGalleryUrl, 
      (
        CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.product_description END
        ) AS ContentIntro,
        (
          CASE 
          WHEN _text_sql_choice = 0 THEN '' 
          ELSE  pv.product_description END
          ) AS ContentIntroPlain,
      pv.product_url AS ProductUrl,
      pv.erp_id AS ERPId,
      (
        CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.category_names END
        ) AS CategoryName, 
      (
        CASE 
        WHEN _text_sql_choice = 0 THEN '' 
        ELSE  pv.tag_names END
        ) AS TagName, 
      '0' AS Seasonality, 
      pv.content_created AS ContentCreated,
      pv.content_last_updated AS ContentLastUpdated,
      pv.ProdIdGraphUUID AS ProdIdGraphUUID, 
      pvp.PPIdGraphUUID AS PPIdGraphUUID
  FROM 
  ".$sp_prefix."aieo_core_product_vitals pv
  INNER JOIN 
  ".$sp_prefix."aieo_core_product_vitals pvp ON pv.parent_product_id = pvp.product_id;
      END;       
      ";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_Export_Orders_HPOS($sp_prefix, $sp_orders_table)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_Export_Orders_HPOS(
    IN _price_sql_choice INT, 
    IN _text_sql_choice INT, 
    IN _full_text_sql_choice INT,
    IN _eponymous_sql_choice INT, 
    IN _seasonality_sql_choice INT, 
    IN _order_id INT, 
    IN _max_records INT
  ) BEGIN 
  TRUNCATE TABLE $sp_orders_table;
  INSERT INTO $sp_orders_table
  (
    OrderIdGraphUUID,
    OrderItemIdGraphUUID,
    PPIdGraphUUID,
    ProdIdGraphUUID,
    EpoIdGraphUUID,
    O_PP_GraphUUID,
    O_Prod_GraphUUID,
    O_Epo_GraphUUID,
    PP_Epo_GraphUUID,
    Prod_Epo_GraphUUID,
    OriginId,
    OrderId,
    OrderStatus,
    SKU,
    ParentSKU,
    ProductId,
    ParentProductId,
    CategoryId,
    TagId,
    OrderDate,
    PrimaryQty,
    ItemSequence,
    Stock,
    OrderSize,
    JourneyLinearWeight,
    AnonymousWeight,
    Price,
    OnSale,
    RoleBasedSpecialPricing,
    LineValue,
    OrderItemsTotal,
    ValueLinearWeight,
    CurrentPrice,
    CurrentRegularPrice,
    ItemName,
    ParentItemName,
    ItemNameDiff,
    ProfitabilityIndex,
    Vendor,
    VendorId,
    Brand,
    BrandId,
    BrandSlug,
    MenuId,
    MenuCategory,
    HeadId,
    HeadCategory,
    SubcatId,
    Subcategory,
    ContentIntro,
    ContentIntroPlain,
    ContentCreated,
    ContentLastUpdated,
    CategoryName,
    TagName,
    EponymousID,
    Seasonality,
    CustomerName,
    CustomerEmail,
    CustomerPhone,
    CustomerPostalCode
)
SELECT 
UNHEX(REPLACE(UUID(), '-', '')) AS OrderIdGraphUUID, 
UNHEX(MD5(CONCAT(opl.order_item_id))) AS OrderItemIdGraphUUID,
COALESCE(var_catalog.PPIdGraphUUID, simple_catalog.PPIdGraphUUID) AS PPIdGraphUUID,
COALESCE(var_catalog.ProdIdGraphUUID, simple_catalog.ProdIdGraphUUID) AS ProdIdGraphUUID,
UNHEX(MD5(CONCAT(opl.customer_id))) AS EpoIdGraphUUID,
UNHEX(MD5(CONCAT(COALESCE(var_catalog.ParentProductId, simple_catalog.ParentProductId), opl.order_id))) AS O_PP_GraphUUID,
UNHEX(MD5(CONCAT(COALESCE(var_catalog.ProductId, simple_catalog.ProductId), opl.order_id))) AS O_Prod_GraphUUID,
UNHEX(MD5(CONCAT(opl.order_id, opl.customer_id))) AS O_Epo_GraphUUID,
UNHEX(MD5(CONCAT(COALESCE(var_catalog.ParentProductId, simple_catalog.ParentProductId), opl.customer_id))) AS PP_Epo_GraphUUID,
UNHEX(MD5(CONCAT(COALESCE(var_catalog.ProductId, simple_catalog.ProductId), opl.customer_id))) AS Prod_Epo_GraphUUID,
'1' AS OriginId,
opl.order_id AS OrderId, 
os.status AS OrderStatus, 
COALESCE(var_catalog.SKU, simple_catalog.SKU) AS SKU,
COALESCE(var_catalog.ParentSKU, simple_catalog.ParentSKU) AS ParentSKU,
COALESCE(var_catalog.ProductId, simple_catalog.ProductId) AS ProductId,
COALESCE(var_catalog.ParentProductId, simple_catalog.ParentProductId) AS ParentProductId,
COALESCE(var_catalog.CategoryId, simple_catalog.CategoryId) AS CategoryId, 
COALESCE(var_catalog.TagId, simple_catalog.TagId) AS TagId, 
os.date_created AS OrderDate, 
opl.product_qty AS PrimaryQty, 
ROW_NUMBER() OVER (PARTITION BY opl.order_id ORDER BY opl.order_item_id) AS ItemSequence,
COALESCE(var_catalog.Stock, simple_catalog.Stock) AS Stock,
COUNT(opl.order_item_id) OVER (PARTITION BY opl.order_id) AS OrderSize,
CASE 
  WHEN COUNT(opl.order_item_id) OVER (PARTITION BY opl.order_id) = 0 THEN 0
  ELSE CAST((CAST(ROW_NUMBER() OVER (PARTITION BY opl.order_id ORDER BY opl.order_item_id) AS DECIMAL(10,6)) / COUNT(opl.order_item_id) OVER (PARTITION BY opl.order_id)) AS DECIMAL(10,6))
END AS JourneyLinearWeight,
'1' AS AnonymousWeight,

(CASE 
  WHEN opl.product_qty = 0 OR _price_sql_choice = 0 THEN 0
  ELSE CAST((opl.product_net_revenue / opl.product_qty) AS decimal(10,2))
END) AS Price,
COALESCE(var_catalog.OnSale, simple_catalog.OnSale) AS OnSale,
COALESCE(var_catalog.RoleBasedSpecialPricing, simple_catalog.RoleBasedSpecialPricing) AS RoleBasedSpecialPricing,
(CASE WHEN _price_sql_choice = 0 THEN ' ' ELSE opl.product_net_revenue END) AS LineValue,
os.net_total AS OrderItemsTotal,
CASE 
  WHEN os.net_total = 0 OR _price_sql_choice = 0 THEN 0
  ELSE CAST((opl.product_net_revenue / os.net_total) AS DECIMAL(10,6))
END AS ValueLinearWeight,
COALESCE(var_catalog.CurrentPrice, simple_catalog.CurrentPrice) AS CurrentPrice,
COALESCE(var_catalog.CurrentRegularPrice, simple_catalog.CurrentRegularPrice) AS CurrentRegularPrice,
COALESCE(var_catalog.ItemName, simple_catalog.ItemName) AS ItemName,
COALESCE(var_catalog.ParentItemName, simple_catalog.ParentItemName) AS ParentItemName,
'' AS ItemNameDiff,
'1' AS ProfitabilityIndex,
COALESCE(var_catalog.Vendor, simple_catalog.Vendor) AS Vendor,
COALESCE(var_catalog.VendorId, simple_catalog.VendorId) AS VendorId,
COALESCE(var_catalog.Brand, simple_catalog.Brand) AS Brand,
COALESCE(var_catalog.BrandId, simple_catalog.BrandId) AS BrandId,
COALESCE(var_catalog.BrandSlug, simple_catalog.BrandSlug) AS BrandSlug,
COALESCE(var_catalog.MenuId, simple_catalog.MenuId) AS MenuId,
COALESCE(var_catalog.MenuCategory, simple_catalog.MenuCategory) AS MenuCategory,
COALESCE(var_catalog.HeadId, simple_catalog.HeadId) AS HeadId,
COALESCE(var_catalog.HeadCategory, simple_catalog.HeadCategory) AS HeadCategory,
COALESCE(var_catalog.SubcatId, simple_catalog.SubcatId) AS SubcatId,
COALESCE(var_catalog.Subcategory, simple_catalog.Subcategory) AS Subcategory,
COALESCE(var_catalog.ContentIntro, simple_catalog.ContentIntro) AS ContentIntro,
COALESCE(var_catalog.ContentIntroPlain, simple_catalog.ContentIntroPlain) AS ContentIntroPlain,
COALESCE(var_catalog.ContentCreated, simple_catalog.ContentCreated) AS ContentCreated,
COALESCE(var_catalog.ContentLastUpdated, simple_catalog.ContentLastUpdated) AS ContentLastUpdated,
COALESCE(var_catalog.CategoryName, simple_catalog.CategoryName) AS CategoryName,
COALESCE(var_catalog.TagName, simple_catalog.TagName) AS TagName,
(CASE WHEN _eponymous_sql_choice = 0 THEN '' ELSE opl.customer_id END) AS EponymousID,
(CASE WHEN _seasonality_sql_choice = 0 THEN '' ELSE '1' END) AS Seasonality,
(CASE WHEN _eponymous_sql_choice = 0 THEN '' ELSE CONCAT(cust.first_name, ' ', cust.last_name) END) AS CustomerName,
(CASE WHEN _eponymous_sql_choice = 0 THEN '' ELSE cust.email END) AS CustomerEmail,
(CASE WHEN _eponymous_sql_choice = 0 THEN '' ELSE cust.phone END) AS CustomerPhone,
(CASE WHEN _eponymous_sql_choice = 0 THEN '' ELSE cust.postcode END) AS CustomerPostalCode
FROM ".$sp_prefix."wc_order_product_lookup opl
JOIN ".$sp_prefix."wc_order_stats os ON os.order_id = opl.order_id
JOIN ".$sp_prefix."wc_order_addresses cust ON cust.order_id = opl.order_id AND cust.address_type = 'billing'
LEFT JOIN ".$sp_prefix."aieo_core_reciprocal_products rprod
    ON rprod.old_product_id = opl.product_id
LEFT JOIN ".$sp_prefix."aieo_temp_product_catalogue simple_catalog
    ON simple_catalog.ProductId = COALESCE(rprod.product_id, opl.product_id) AND opl.variation_id = 0
LEFT JOIN ".$sp_prefix."aieo_temp_product_catalogue var_catalog
    ON var_catalog.ProductId = COALESCE(rprod.parent_product_id, opl.variation_id) AND opl.variation_id != 0
WHERE cust.email IS NOT NULL 
AND opl.order_id >= _order_id
-- AND (simple_catalog.ProductId IS NOT NULL OR var_catalog.ProductId IS NOT NULL)
LIMIT _max_records;  
      END;";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_Export_Store_Orders($sp_prefix, $sp_store_orders_table, $sp_erp_store_products_table_name, $sp_erp_store_orders_table_name, $sp_erp_store_customers_table_name)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_Export_Store_Orders(
    IN _price_sql_choice INT, 
    IN _eponymous_sql_choice INT, 
    IN _seasonality_sql_choice INT,
    IN _days_past_sql_choice INT,
    IN _days_after_sql_choice INT,
    IN _instore_invoices_sql_choice INT,
    IN _instore_invoices_list_sql_choice VARCHAR(255),
    IN _instore_anonymous_sql_choice INT,
    IN _instore_anonymous_list_sql_choice VARCHAR(255)
  ) BEGIN 

  TRUNCATE TABLE $sp_store_orders_table;
  
  INSERT INTO $sp_store_orders_table
  (
    OrderStatus,
    StoreCustomerID,
    LoyaltyId,
    CustomerName,
    CustomerEmail,
    CustomerPhone,
    CustomerPostalCode,
    CustomerStoreSince,
    CustomerLoyaltySince,
    LoyaltyPoints,
    LastTransactionDate,
    LastTransactionLocation,
    OrderId,
    OriginId,
    OriginType,
    OrderDate,
    EventLocation,
    LineValue,
    PrimaryQty,
    Price,
    OrderItemsTotal, 
    ValueLinearWeight,
    ProductId,
    ParentProductId,
    ItemName,
    ParentItemName,
    ProductURL,
    ImageURL,
    CurrentPrice,
    CurrentRegularPrice,
    RoleBasedSpecialPricing,
    Seasonality,
    Vendor,
    VendorId,
    Brand,
    BrandId,
    BrandSlug,
    CategoryId,
    CategoryName,
    TagId,
    TagName,
    MenuId,
    MenuCategory,
    HeadId,
    HeadCategory,
    SubcatId,
    SubCategory,
    Stock,
    Sku,
    ParentSku
    )
    SELECT 
    (CASE 
    WHEN o.Quantity < 0 THEN 'erp-returned'
    ELSE 'erp-completed'
END) AS OrderStatus,
        c.CustomerId as StoreCustomerID,
        c.LoyaltyId AS LoyaltyId,
        c.CustomerName AS CustomerName,
        c.CustomerEmail AS CustomerEmail,
        c.CustomerPhone AS CustomerPhone,
        c.CustomerPostalCode AS CustomerPostalCode,
        c.CustomerFirstTransaction AS CustomerStoreSince,
        c.CustomerLoyaltySince AS CustomerLoyaltySince,
        c.BonusPointsTotal AS LoyaltyPoints,
        c.CustomerLatestTransaction AS LastTransactionDate,
        c.CustomerLatestLocation AS LastTransactionLocation,
        o.OrderId,
        '2' as OriginId,
        o.InvoiceType as OriginType, 
        o.OrderDate AS OrderDate,
        o.Branch AS EventLocation,
        o.Line_price AS LineValue,
        o.Quantity AS PrimaryQty,
        (CASE 
            WHEN _price_sql_choice = 0 OR o.Quantity IS NULL OR o.Quantity <= 0 THEN 0
            ELSE CAST((o.Line_price / NULLIF(o.Quantity, 0)) AS DECIMAL(10,2))
        END) AS Price,
        (CASE 
            WHEN _price_sql_choice = 0 THEN 0 ELSE 
         CAST(SUM(CASE WHEN o.Line_price >= 0 THEN o.Line_price ELSE 0 END) OVER (PARTITION BY c.CustomerId, o.OrderId) AS DECIMAL(10,6)) END) AS OrderItemsTotal,
        (CASE 
            WHEN  _price_sql_choice = 0 OR o.Quantity IS NULL OR o.Quantity <= 0 THEN 0
            ELSE CAST((o.Line_price / NULLIF(SUM(CASE WHEN o.Line_price >= 0 THEN o.Line_price ELSE 0 END) OVER (PARTITION BY c.CustomerId, o.OrderId), 0)) AS DECIMAL(10,6))
        END) AS ValueLinearWeight,
        p.ProductId AS ProductId,
        p.ParentProductId AS ParentProductId,
        p.ItemName AS ItemName,
        p.ParentItemName AS ParentItemName,
        p.ProductURL AS ProductURL,
        p.ImageURL AS ImageURL,
        p.CurrentPrice AS CurrentPrice,
        p.CurrentRegularPrice AS CurrentRegularPrice,
        p.RoleBasedSpecialPricing AS RoleBasedSpecialPricing,
        _seasonality_sql_choice AS Seasonality,
        p.Vendor AS Vendor,
        p.VendorId AS VendorId,
        p.Brand AS Brand,
        p.BrandId AS BrandId,
        p.BrandSlug AS BrandSlug,
        p.CategoryId AS CategoryId,
        p.CategoryName AS CategoryName,
        p.TagId AS TagId,
        p.TagName AS TagName,
        p.MenuId AS MenuId,
        p.MenuCategory AS MenuCategory,
        p.HeadId AS HeadId,
        p.HeadCategory AS HeadCategory,
        p.SubcatId AS SubcatId,
        p.SubCategory AS SubCategory,
        p.Stock AS Stock,
        o.sku AS Sku, 
        p.ParentSku AS ParentSku
    FROM 
    ".$sp_erp_store_customers_table_name." c
    JOIN
    ".$sp_erp_store_orders_table_name." o ON c.Loyaltyid = o.LoyaltyId AND o.Line_price  > 0
    LEFT JOIN
    ".$sp_prefix."aieo_temp_product_catalogue p ON o.SKU = p.sku 
    WHERE
    (
      _seasonality_sql_choice <> 1 OR
      (
          (
              DATE_FORMAT(o.OrderDate, '%m-%d') >= DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL _days_past_sql_choice DAY), '%m-%d')
              AND DATE_FORMAT(o.OrderDate, '%m-%d') <= DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL _days_after_sql_choice DAY), '%m-%d')
          )
          OR
          (
              DATE_FORMAT(o.OrderDate, '%m-%d') >= DATE_FORMAT(DATE_SUB(CONCAT(YEAR(o.OrderDate),'-',MONTH(CURDATE()),'-',DAY(CURDATE())), INTERVAL _days_past_sql_choice DAY), '%m-%d')
              AND DATE_FORMAT(o.OrderDate, '%m-%d') <= DATE_FORMAT(DATE_ADD(CONCAT(YEAR(o.OrderDate),'-',MONTH(CURDATE()),'-',DAY(CURDATE())), INTERVAL _days_after_sql_choice DAY), '%m-%d')
         )
    )
    )
    AND
    (
        _instore_invoices_sql_choice <> 1 OR
        NOT FIND_IN_SET(o.InvoiceType, REPLACE(_instore_invoices_list_sql_choice, ' ', ''))
    );  
    
    

    
INSERT INTO $sp_store_orders_table
  (
    OrderStatus,
    StoreCustomerID,
    EponymousID,
    OrderId,
    OriginId,
    OriginType,
    OrderDate,
    EventLocation,
    LineValue,
    PrimaryQty,
    Price,
    OrderItemsTotal, 
    ValueLinearWeight,
    ProductId,
    ParentProductId,
    ItemName,
    ParentItemName,
    ProductURL,
    ImageURL,
    CurrentPrice,
    CurrentRegularPrice,
    RoleBasedSpecialPricing,
    Seasonality,
    Vendor,
    VendorId,
    Brand,
    BrandId,
    BrandSlug,
    CategoryId,
    CategoryName,
    TagId,
    TagName,
    MenuId,
    MenuCategory,
    HeadId,
    HeadCategory,
    SubcatId,
    SubCategory,
    Stock,
    Sku,
    ParentSku
    )
    SELECT 
        (CASE 
        WHEN o.Quantity < 0 THEN 'erp-returned'
        ELSE 'erp-completed'
        END) AS OrderStatus,
        o.CustomerId as StoreCustomerID,
        '0' as EponymousId,
        o.OrderId,
        '2' as OriginId,
        o.InvoiceType as OriginType, 
        o.OrderDate AS OrderDate,
        o.Branch AS EventLocation,
        o.Line_price AS LineValue,
        0 AS PrimaryQty,
        0 AS Price,
        0 AS OrderItemsTotal,
        0 AS ValueLinearWeight,
        p.ProductId AS ProductId,
        p.ParentProductId AS ParentProductId,
        p.ItemName AS ItemName,
        p.ParentItemName AS ParentItemName,
        p.ProductURL AS ProductURL,
        p.ImageURL AS ImageURL,
        p.CurrentPrice AS CurrentPrice,
        p.CurrentRegularPrice AS CurrentRegularPrice,
        p.RoleBasedSpecialPricing AS RoleBasedSpecialPricing,
        _seasonality_sql_choice AS Seasonality,
        p.Vendor AS Vendor,
        p.VendorId AS VendorId,
        p.Brand AS Brand,
        p.BrandId AS BrandId,
        p.BrandSlug AS BrandSlug,
        p.CategoryId AS CategoryId,
        p.CategoryName AS CategoryName,
        p.TagId AS TagId,
        p.TagName AS TagName,
        p.MenuId AS MenuId,
        p.MenuCategory AS MenuCategory,
        p.HeadId AS HeadId,
        p.HeadCategory AS HeadCategory,
        p.SubcatId AS SubcatId,
        p.SubCategory AS SubCategory,
        p.Stock AS Stock,
        o.sku AS Sku, 
        p.ParentSku AS ParentSku
    FROM
    ".$sp_erp_store_orders_table_name." o
    LEFT JOIN
    ".$sp_prefix."aieo_temp_product_catalogue p ON o.SKU = p.sku 
    WHERE
    (
      _seasonality_sql_choice <> 1 OR
      (
          (
              DATE_FORMAT(o.OrderDate, '%m-%d') >= DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL _days_past_sql_choice DAY), '%m-%d')
              AND DATE_FORMAT(o.OrderDate, '%m-%d') <= DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL _days_after_sql_choice DAY), '%m-%d')
          )
          OR
          (
              DATE_FORMAT(o.OrderDate, '%m-%d') >= DATE_FORMAT(DATE_SUB(CONCAT(YEAR(o.OrderDate),'-',MONTH(CURDATE()),'-',DAY(CURDATE())), INTERVAL _days_past_sql_choice DAY), '%m-%d')
              AND DATE_FORMAT(o.OrderDate, '%m-%d') <= DATE_FORMAT(DATE_ADD(CONCAT(YEAR(o.OrderDate),'-',MONTH(CURDATE()),'-',DAY(CURDATE())), INTERVAL _days_after_sql_choice DAY), '%m-%d')
         )
    )
    )
    AND (
      FIND_IN_SET(o.CustomerId, REPLACE(_instore_anonymous_list_sql_choice, ' ', ''))
      AND _instore_anonymous_sql_choice = 1 
  );
    END;";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_Merge_Store_Orders($sp_prefix)
{
  global $wpdb;

  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_Merge_Store_Orders (
    IN _store_orders_table_name VARCHAR(255),
    IN _temp_orders_table_name VARCHAR(255),
    IN _analytics_eponymous_id_field VARCHAR(255),
    IN instore_product_threshold_sql_choice INT,
    IN instore_product_threshold DECIMAL(10, 2),
    IN instore_products_list_sql_choice INT,
    IN instore_products_list TEXT
  )
  BEGIN
    DECLARE query TEXT;
    DECLARE exclude_skus TEXT;

    -- Prepare the exclusion list for SKUs
    IF instore_products_list_sql_choice = 1 THEN
      SET exclude_skus = REPLACE(instore_products_list, ',', ''',''');
      SET exclude_skus = CONCAT('''', exclude_skus, '''');
    END IF;

    SET query = CONCAT('
      INSERT INTO ', _temp_orders_table_name, ' (
        OrderStatus,
        StoreCustomerID,
        LoyaltyId,
        CustomerName,
        CustomerEmail,
        CustomerPhone,
        CustomerPostalCode,
        CustomerStoreSince,
        CustomerLoyaltySince,
        LoyaltyPoints,
        LastTransactionDate,
        LastTransactionLocation,
        OrderId,
        OriginId,
        OriginType,
        OrderDate,
        EventLocation,
        LineValue,
        PrimaryQty,
        Price,
        OrderItemsTotal, 
        ValueLinearWeight,
        ProductId,
        ParentProductId,
        ItemName,
        ParentItemName,
        ProductURL,
        ImageURL,
        CurrentPrice,
        CurrentRegularPrice,
        RoleBasedSpecialPricing,
        Seasonality,
        Vendor,
        VendorId,
        Brand,
        BrandId,
        BrandSlug,
        CategoryId,
        CategoryName,
        TagId,
        TagName,
        MenuId,
        MenuCategory,
        HeadId,
        HeadCategory,
        SubcatId,
        SubCategory,
        Stock,
        Sku,
        ParentSku,
        EponymousId
      )
      SELECT 
        OrderStatus,
        StoreCustomerID,
        LoyaltyId,
        CustomerName,
        CustomerEmail,
        CustomerPhone,
        CustomerPostalCode,
        CustomerStoreSince,
        CustomerLoyaltySince,
        LoyaltyPoints,
        LastTransactionDate,
        LastTransactionLocation,
        OrderId,
        OriginId,
        OriginType,
        OrderDate,
        EventLocation,
        LineValue,
        PrimaryQty,
        Price,
        OrderItemsTotal, 
        ValueLinearWeight,
        ProductId,
        ParentProductId,
        ItemName,
        ParentItemName,
        ProductURL,
        ImageURL,
        CurrentPrice,
        CurrentRegularPrice,
        RoleBasedSpecialPricing,
        Seasonality,
        Vendor,
        VendorId,
        Brand,
        BrandId,
        BrandSlug,
        CategoryId,
        CategoryName,
        TagId,
        TagName,
        MenuId,
        MenuCategory,
        HeadId,
        HeadCategory,
        SubcatId,
        SubCategory,
        Stock,
        Sku,
        ParentSku,
        ', _analytics_eponymous_id_field, ' AS EponymousId
      FROM ', _store_orders_table_name,
      ' WHERE 1 = 1');

    -- Add condition for product threshold
    IF instore_product_threshold_sql_choice = 1 THEN
      SET query = CONCAT(query, ' AND LineValue > ', instore_product_threshold);
    END IF;
    
    -- Add condition to exclude specific SKUs
    IF instore_products_list_sql_choice = 1 THEN
      SET query = CONCAT(query, ' AND Sku NOT IN (', exclude_skus, ')');
    END IF;

    -- Execute the dynamic SQL statement
    SET @sql = query;
    PREPARE stmt FROM @sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  
  END;
  ";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_Export_Orders_non_HPOS($sp_prefix, $sp_orders_table)
{
  global $wpdb;
  
  
  
  $sql = "CREATE PROCEDURE " . $sp_prefix . "AIEO_Export_Orders_non_HPOS(
        IN _price_sql_choice INT, 
        IN _text_sql_choice INT, 
        IN _full_text_sql_choice INT,
        IN _eponymous_sql_choice INT, 
        IN _seasonality_sql_choice INT, 
        IN _order_id INT, 
        IN _max_records INT
  ) BEGIN 
  TRUNCATE TABLE $sp_orders_table;
  INSERT INTO $sp_orders_table
(OrderId, OrderStatus, SKU,  ProductId, ParentProductId, CategoryId, TagId, OrderDate, PrimaryQty, ItemSequence, Stock, OrderSize, 
JourneyLinearWeight, AnonymousWeight, Price, LineValue, OrderItemsTotal, ValueLinearWeight, CurrentPrice, CurrentRegularPrice, ItemName,  ParentItemName, ItemNameDiff, 
ProfitabilityIndex, Brand, ContentIntro, ContentIntroPlain, ContentCreated, ContentLastUpdated, 
CategoryName, TagName, EponymousID, Seasonality)

SELECT  o.id as OrderId,
o.post_status as OrderStatus,
case 
when _text_sql_choice = 0
then ''
else COALESCE(psku.meta_value, '')
end as SKU,
COALESCE(case 
when oimpv.meta_value = 0
then oimp.meta_value
else oimpv.meta_value
end, '') as ProductId,
COALESCE(oimp.meta_value, '') as ParentProductId,
COALESCE(pc.category_ids, '') as CategoryId,
COALESCE(pt.tag_ids, '') as TagId,
o.post_date_gmt as OrderDate,
COALESCE(CONVERT(oimq.meta_value, decimal(3, 0)), '') as PrimaryQty,
ROW_NUMBER() over (
partition by o.id order by oi.order_item_id
) as ItemSequence,
COALESCE(pstock.meta_value, '') as Stock,
os.OrderSize,
ROW_NUMBER() over (
partition by o.id order by oi.order_item_id
) / os.OrderSize as JourneyLinearWeight,
(
case 
when _eponymous_sql_choice = 0
then '0'
else '1'
end
) as AnonymousWeight,
(
case 
when _price_sql_choice = 0
then ''
else convert(oims.meta_value / oimq.meta_value, decimal(5, 2))
end
) as Price,
(
case 
when _price_sql_choice = 0
then ''
else convert(oims.meta_value, decimal(7, 2))
end
) as LineValue,
( CASE WHEN _price_sql_choice = 0 THEN '' ELSE convert(pmot.meta_value , 
decimal(7, 2)) END ) as OrderItemsTotal,
(
case 
when _price_sql_choice = 0
then ''
else (
case 
when pmot.meta_value = 0
or pmot.meta_value is null
then '0'
else convert(oims.meta_value / pmot.meta_value, decimal(7, 2))
end
)
end
) as ValueLinearWeight,
(
    CASE 
      WHEN _price_sql_choice = 0 THEN '' 
      ELSE (
        CASE 
          WHEN pm_price.meta_value REGEXP '^[0-9.]+$' THEN CONVERT(pm_price.meta_value, DECIMAL(7, 2)) 
          ELSE NULL 
        END 
      ) 
  END
  ) AS CurrentPrice,
  
  (
    CASE 
      WHEN _price_sql_choice = 0 THEN '' 
      ELSE (
        CASE 
          WHEN pm_regular_price.meta_value REGEXP '^[0-9.]+$' THEN CONVERT(pm_regular_price.meta_value, DECIMAL(7, 2)) 
          ELSE NULL 
        END
      ) 
  END
) AS CurrentRegularPrice,
(
case 
when _text_sql_choice = 0
then ''
else oi.order_item_name
end
) as ItemName,
(
case 
when _text_sql_choice = 0
then ''
else (
case 
when oimpv.meta_value = 0
then oi.order_item_name
else pmp.post_title
end
)
end
) as ParentItemName,
'' as ItemNameDiff,

  '1' as ProfitabilityIndex,
'' as Brand,
(
case 
when _full_text_sql_choice = 0
then ''
else (
case 
when oimpv.meta_value = 0
then pmp.post_content
else (
select parent.post_content
from wp_posts parent
where parent.ID = oimp.meta_value
)
end
)
end
) as ContentIntro,
(
    case 
    when _full_text_sql_choice = 0
    then ''
    else (
    case 
    when oimpv.meta_value = 0
    then pmp.post_content
    else (
    select parent.post_content
    from wp_posts parent
    where parent.ID = oimp.meta_value
    )
    end
    )
    end
    ) as ContentIntroPlain,
COALESCE(pmp.post_date_gmt, '2000-01-01 00:00:00') as ContentCreated,
COALESCE(pmp.post_modified_gmt, '2000-01-01 00:00:00') as ContentLastUpdated,
(
case 
when _text_sql_choice = 0
then ''
else COALESCE(pc.category_names, '')
end
) as CategoryName,

(
case 
when _text_sql_choice = 0
then ''
else COALESCE(pt.tag_names, '')
end
) as TagName,
(
case 
when _eponymous_sql_choice = 0
then '0'
else pmu.meta_value
end
) as EponymousID,
(
case 
when _seasonality_sql_choice = 0
then '0'
else '1'
end
) as Seasonality
FROM wp_woocommerce_order_items oi

INNER JOIN wp_posts o
ON o.ID = oi.order_id
AND o.post_type = 'shop_order'

INNER JOIN wp_woocommerce_order_itemmeta oimp
ON oi.order_item_id = oimp.order_item_id
AND oimp.meta_key = '_product_id'

LEFT JOIN wp_woocommerce_order_itemmeta oimpv
ON oi.order_item_id = oimpv.order_item_id
AND oimpv.meta_key = '_variation_id'

LEFT JOIN wp_woocommerce_order_itemmeta oimq
ON oi.order_item_id = oimq.order_item_id
AND oimq.meta_key = '_qty'

LEFT JOIN wp_woocommerce_order_itemmeta oims
ON oi.order_item_id = oims.order_item_id
AND oims.meta_key = '_line_subtotal'

INNER JOIN wp_postmeta as pmu
ON pmu.post_id = o.ID
AND pmu.meta_key = '_customer_user'

INNER JOIN wp_postmeta as pmot
ON pmot.post_id = o.ID
AND pmot.meta_key = '_order_total'

LEFT JOIN wp_posts pmp
ON pmp.ID = oimp.meta_value

LEFT JOIN wp_postmeta as psku
ON psku.post_id = CASE 
    WHEN oimpv.meta_value != '0' THEN oimpv.meta_value
    ELSE pmp.ID
END
AND psku.meta_key = '_sku'
LEFT JOIN wp_postmeta AS pm_price 
ON (
    CASE 
        WHEN oimpv.meta_value != '0' THEN oimpv.meta_value  
        ELSE oimp.meta_value                               
    END
) = pm_price.post_id 
AND pm_price.meta_key = '_price'

LEFT JOIN wp_postmeta AS pm_regular_price 
ON (
    CASE 
        WHEN oimpv.meta_value IS NOT NULL AND oimpv.meta_value != '0' THEN oimpv.meta_value
        ELSE oimp.meta_value
    END
) = pm_regular_price.post_id 
AND pm_regular_price.meta_key = '_regular_price'

LEFT JOIN wp_postmeta pstock
ON (
    CASE 
        WHEN oimpv.meta_value != '0' THEN oimpv.meta_value  
        ELSE oimp.meta_value                               
    END
) = pstock.post_id 
AND pstock.meta_key = '_stock'

left join (
select oi.order_id,
COUNT(distinct oimp.meta_value) as OrderSize
from wp_woocommerce_order_items oi
inner join wp_woocommerce_order_itemmeta oimp
on oi.order_item_id = oimp.order_item_id
and oimp.meta_key = '_product_id'
group by oi.order_id
) as os
on o.ID = os.order_id
left join (
select tr.object_id as product_id,
GROUP_CONCAT(t.term_id order by t.term_id asc) as category_ids,
GROUP_CONCAT(t.name order by t.term_id asc) as category_names -- Include category names
from wp_term_relationships tr
inner join wp_term_taxonomy tt
on tr.term_taxonomy_id = tt.term_taxonomy_id
and tt.taxonomy = 'product_cat'
inner join wp_terms t
on tt.term_id = t.term_id
group by tr.object_id
) as pc
on pc.product_id = oimp.meta_value
left join (
select tr.object_id as product_id,
GROUP_CONCAT(t.term_id order by t.term_id asc) as tag_ids,
GROUP_CONCAT(t.name order by t.term_id asc) as tag_names
from wp_term_relationships tr
inner join wp_term_taxonomy tt
on tr.term_taxonomy_id = tt.term_taxonomy_id
and tt.taxonomy = 'product_tag'
inner join wp_terms t
on tt.term_id = t.term_id
group by tr.object_id
) as pt
on pt.product_id = oimp.meta_value
where oi.order_item_type = 'line_item'
and oi.order_id >= _order_id
LIMIT _max_records;

END;      
    ";

  $wpdb->query($sql);
}

function aieo_create_sp_AIEO_InsertCoreProductAttributes($sp_prefix, $sp_core_product_attributes_table_name)
{
    global $wpdb;

    $procedure_name = $sp_prefix . "AIEO_InsertCoreProductAttributes";

    $sql = "CREATE PROCEDURE $procedure_name(
                IN vendorTaxonomy VARCHAR(255),
                IN brandTaxonomy VARCHAR(255),
                IN sizeTaxonomies VARCHAR(255),
                IN colorTaxonomies VARCHAR(255)
            )
            BEGIN
                -- Simplified brand query - populate all products with brands if available
                -- Uses subquery to ensure ONE brand per product
                TRUNCATE TABLE $sp_core_product_attributes_table_name;

                INSERT INTO $sp_core_product_attributes_table_name
                  (name, product_id, parent_product_id, effective_product_id,
                   brand_id, brand_name,
                   product_stock, product_count,
                   supplier_id, supplier,
                   brand_bias, campaigns)
                SELECT
                    COALESCE(brands.brand_name, 'No Brand') AS name,
                    p.ID AS product_id,
                    CASE WHEN p.post_parent = 0 THEN p.ID ELSE p.post_parent END AS parent_product_id,
                    CASE WHEN p.post_parent = 0 THEN p.ID ELSE p.post_parent END AS effective_product_id,
                    COALESCE(brands.brand_id, 0) AS brand_id,
                    COALESCE(brands.brand_slug, 'no-brand') AS brand_name,
                    '0' AS product_stock,
                    '0' AS product_count,
                    NULL AS supplier_id,
                    NULL AS supplier,
                    NULL AS brand_bias,
                    '' AS campaigns
                FROM " . $sp_prefix . "posts p
                LEFT JOIN (
                    SELECT
                        tr.object_id AS product_id,
                        t.name AS brand_name,
                        t.term_id AS brand_id,
                        t.slug AS brand_slug
                    FROM " . $sp_prefix . "term_relationships tr
                    INNER JOIN " . $sp_prefix . "term_taxonomy x
                        ON x.term_taxonomy_id = tr.term_taxonomy_id
                        AND x.taxonomy = brandTaxonomy
                    INNER JOIN " . $sp_prefix . "terms t
                        ON t.term_id = x.term_id
                    GROUP BY tr.object_id
                ) AS brands ON brands.product_id = p.ID
                WHERE p.post_type IN ('product', 'product_variation')
                  AND p.post_status = 'publish'
                LIMIT 5000000;
            END";

    aieo_drop_sp_AIEO_InsertCoreProductAttributes($sp_prefix);
    $wpdb->query($sql);
}

function aieo_create_sp_AIEO_InsertProductCategoryHierarchy($sp_prefix, $sp_core_product_category_hierarchy_table_name)
{
    global $wpdb;

    $procedure_name = $sp_prefix . "AIEO_InsertProductCategoryHierarchy";

    $sql = "CREATE PROCEDURE $procedure_name()
            BEGIN
                -- Optimized hierarchy logic from generateproductcathierarchy.sql
                TRUNCATE TABLE $sp_core_product_category_hierarchy_table_name;

                INSERT INTO $sp_core_product_category_hierarchy_table_name
                (
                  name,
                  product_id,
                  top_category_id,
                  top_category,
                  second_level_category_id,
                  second_level_category,
                  third_level_category_id,
                  third_level_category,
                  fourth_level_category_id,
                  fourth_level_category,
                  fifth_level_category_id,
                  fifth_level_category,
                  reference_quantity,
                  season_start,
                  season_end,
                  season_midpoint,
                  campaigns,
                  category_ids,
                  google_product_category,
                  facebook_product_category
                )
                SELECT
                  p.ID AS name,
                  p.ID AS product_id,

                  tc.term_id  AS top_category_id,
                  tc.name     AS top_category,

                  sc.term_id  AS second_level_category_id,
                  sc.name     AS second_level_category,

                  thc.term_id AS third_level_category_id,
                  thc.name    AS third_level_category,

                  frc.term_id AS fourth_level_category_id,
                  frc.name    AS fourth_level_category,

                  ffc.term_id AS fifth_level_category_id,
                  ffc.name    AS fifth_level_category,

                  -- Season and campaign fields (not available without Pods framework)
                  NULL AS reference_quantity,
                  NULL AS season_start,
                  NULL AS season_end,
                  NULL AS season_midpoint,
                  NULL AS campaigns,

                  -- Gather all category IDs in a comma-delimited list
                  (
                    SELECT GROUP_CONCAT(DISTINCT tt2.term_id ORDER BY tt2.term_id SEPARATOR ',')
                    FROM " . $sp_prefix . "term_relationships tr2
                    JOIN " . $sp_prefix . "term_taxonomy tt2
                         ON tr2.term_taxonomy_id = tt2.term_taxonomy_id
                    WHERE tr2.object_id = p.ID
                      AND tt2.taxonomy = 'product_cat'
                  ) AS category_ids,

                  -- Google Product Category — leaf-first COALESCE across the
                  -- 5-level category chain. Each level LEFT-JOINs to
                  -- wp_aieo_dmm_category_extras (admin-authored WC-cat →
                  -- Google mapping). The most-specific (deepest) mapping wins;
                  -- if a leaf isn't mapped, inherit from its parent, then
                  -- grandparent, all the way up to the top category.
                  -- NULLIF guards against empty-string saves from the picker.
                  COALESCE(
                    NULLIF(gce_5.google_category_path, ''),
                    NULLIF(gce_4.google_category_path, ''),
                    NULLIF(gce_3.google_category_path, ''),
                    NULLIF(gce_2.google_category_path, ''),
                    NULLIF(gce_1.google_category_path, '')
                  ) AS google_product_category,

                  -- Facebook Marketplace Product Category — identical
                  -- resolution against the facebook_category_path column.
                  COALESCE(
                    NULLIF(fce_5.facebook_category_path, ''),
                    NULLIF(fce_4.facebook_category_path, ''),
                    NULLIF(fce_3.facebook_category_path, ''),
                    NULLIF(fce_2.facebook_category_path, ''),
                    NULLIF(fce_1.facebook_category_path, '')
                  ) AS facebook_product_category

                FROM " . $sp_prefix . "posts p

                -- Each level subquery walks UP the term-taxonomy chain via
                -- explicit self-joins instead of deeply-nested IN subqueries.
                -- The original nested-IN form (matching the canonical
                -- generateproductcathierarchy.sql) triggers a MariaDB 10.6
                -- optimizer pathology: when the deepest LEFT JOIN's inner
                -- subquery returns 0 rows, the planner mis-treats the LEFT
                -- JOIN as INNER and the whole hierarchy collapses to a
                -- single row. Chained JOINs avoid the bug entirely and
                -- are also faster + more readable.
                --
                -- Per-product tie-break stays MIN(term_id) ASC (matches
                -- the canonical script's behaviour).

                -- First (top) level — product directly tagged with parent=0 cat
                JOIN (
                    SELECT ranked.product_id, ranked.term_id, t.name
                    FROM (
                        SELECT tr.object_id AS product_id, tt.term_id,
                               ROW_NUMBER() OVER (PARTITION BY tr.object_id ORDER BY tt.term_id) AS rn
                        FROM " . $sp_prefix . "term_relationships tr
                        JOIN " . $sp_prefix . "term_taxonomy tt
                             ON tr.term_taxonomy_id = tt.term_taxonomy_id
                            AND tt.taxonomy = 'product_cat'
                            AND tt.parent = 0
                    ) ranked
                    JOIN " . $sp_prefix . "terms t ON t.term_id = ranked.term_id
                    WHERE ranked.rn = 1
                ) AS tc
                  ON p.ID = tc.product_id

                -- Second level — leaf whose parent is parent=0
                LEFT JOIN (
                    SELECT ranked.product_id, ranked.term_id, t.name
                    FROM (
                        SELECT tr.object_id AS product_id, tt.term_id,
                               ROW_NUMBER() OVER (PARTITION BY tr.object_id ORDER BY tt.term_id) AS rn
                        FROM " . $sp_prefix . "term_relationships tr
                        JOIN " . $sp_prefix . "term_taxonomy tt
                             ON tr.term_taxonomy_id = tt.term_taxonomy_id
                            AND tt.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p1
                             ON p1.term_id = tt.parent
                            AND p1.taxonomy = 'product_cat'
                            AND p1.parent = 0
                    ) ranked
                    JOIN " . $sp_prefix . "terms t ON t.term_id = ranked.term_id
                    WHERE ranked.rn = 1
                ) AS sc
                  ON p.ID = sc.product_id

                -- Third level — leaf whose grandparent is parent=0
                LEFT JOIN (
                    SELECT ranked.product_id, ranked.term_id, t.name
                    FROM (
                        SELECT tr.object_id AS product_id, tt.term_id,
                               ROW_NUMBER() OVER (PARTITION BY tr.object_id ORDER BY tt.term_id) AS rn
                        FROM " . $sp_prefix . "term_relationships tr
                        JOIN " . $sp_prefix . "term_taxonomy tt
                             ON tr.term_taxonomy_id = tt.term_taxonomy_id
                            AND tt.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p1
                             ON p1.term_id = tt.parent
                            AND p1.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p2
                             ON p2.term_id = p1.parent
                            AND p2.taxonomy = 'product_cat'
                            AND p2.parent = 0
                    ) ranked
                    JOIN " . $sp_prefix . "terms t ON t.term_id = ranked.term_id
                    WHERE ranked.rn = 1
                ) AS thc
                  ON p.ID = thc.product_id

                -- Fourth level
                LEFT JOIN (
                    SELECT ranked.product_id, ranked.term_id, t.name
                    FROM (
                        SELECT tr.object_id AS product_id, tt.term_id,
                               ROW_NUMBER() OVER (PARTITION BY tr.object_id ORDER BY tt.term_id) AS rn
                        FROM " . $sp_prefix . "term_relationships tr
                        JOIN " . $sp_prefix . "term_taxonomy tt
                             ON tr.term_taxonomy_id = tt.term_taxonomy_id
                            AND tt.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p1
                             ON p1.term_id = tt.parent
                            AND p1.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p2
                             ON p2.term_id = p1.parent
                            AND p2.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p3
                             ON p3.term_id = p2.parent
                            AND p3.taxonomy = 'product_cat'
                            AND p3.parent = 0
                    ) ranked
                    JOIN " . $sp_prefix . "terms t ON t.term_id = ranked.term_id
                    WHERE ranked.rn = 1
                ) AS frc
                  ON p.ID = frc.product_id

                -- Fifth level
                LEFT JOIN (
                    SELECT ranked.product_id, ranked.term_id, t.name
                    FROM (
                        SELECT tr.object_id AS product_id, tt.term_id,
                               ROW_NUMBER() OVER (PARTITION BY tr.object_id ORDER BY tt.term_id) AS rn
                        FROM " . $sp_prefix . "term_relationships tr
                        JOIN " . $sp_prefix . "term_taxonomy tt
                             ON tr.term_taxonomy_id = tt.term_taxonomy_id
                            AND tt.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p1
                             ON p1.term_id = tt.parent
                            AND p1.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p2
                             ON p2.term_id = p1.parent
                            AND p2.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p3
                             ON p3.term_id = p2.parent
                            AND p3.taxonomy = 'product_cat'
                        JOIN " . $sp_prefix . "term_taxonomy p4
                             ON p4.term_id = p3.parent
                            AND p4.taxonomy = 'product_cat'
                            AND p4.parent = 0
                    ) ranked
                    JOIN " . $sp_prefix . "terms t ON t.term_id = ranked.term_id
                    WHERE ranked.rn = 1
                ) AS ffc
                  ON p.ID = ffc.product_id

                -- Marketplace-taxonomy mappings per WC-category. Two LEFT
                -- JOINs per breadcrumb level — one each for Google +
                -- Facebook category_extras — so the COALESCE above can
                -- inherit the leaf-most mapping up the category chain.
                -- Joins resolve on the term_id at each level; if a level
                -- isn't reached for this product (e.g. only 3 levels deep)
                -- the joined row is simply absent → NULL → COALESCE skips it.
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras gce_1
                  ON gce_1.taxonomy = 'product_cat' AND gce_1.term_id = tc.term_id
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras gce_2
                  ON gce_2.taxonomy = 'product_cat' AND gce_2.term_id = sc.term_id
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras gce_3
                  ON gce_3.taxonomy = 'product_cat' AND gce_3.term_id = thc.term_id
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras gce_4
                  ON gce_4.taxonomy = 'product_cat' AND gce_4.term_id = frc.term_id
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras gce_5
                  ON gce_5.taxonomy = 'product_cat' AND gce_5.term_id = ffc.term_id
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras fce_1
                  ON fce_1.taxonomy = 'product_cat' AND fce_1.term_id = tc.term_id
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras fce_2
                  ON fce_2.taxonomy = 'product_cat' AND fce_2.term_id = sc.term_id
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras fce_3
                  ON fce_3.taxonomy = 'product_cat' AND fce_3.term_id = thc.term_id
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras fce_4
                  ON fce_4.taxonomy = 'product_cat' AND fce_4.term_id = frc.term_id
                LEFT JOIN " . $sp_prefix . "aieo_dmm_category_extras fce_5
                  ON fce_5.taxonomy = 'product_cat' AND fce_5.term_id = ffc.term_id

                WHERE
                    p.post_type IN ('product', 'product_variation')
                    AND p.post_status = 'publish'

                GROUP BY p.ID
                ORDER BY p.ID;
            END";

    aieo_drop_sp_AIEO_InsertProductCategoryHierarchy($sp_prefix);
    $wpdb->query($sql);
}

function aieo_create_sp_AIEO_InsertProductVitals($sp_prefix, $sp_core_product_vitals_table_name, $domain_name)
{
    global $wpdb;

    
    $sp_core_product_attributes_table_name = get_option('aieo_core_product_attributes_table');
    $sp_core_product_category_hierarchy_table_name = get_option('aieo_core_product_category_hierarchy_table');

    $procedure_name = $sp_prefix . "AIEO_InsertProductVitals";

    $sql = "CREATE PROCEDURE $procedure_name()
            BEGIN
                -- Optimized logic from generatefastfinderv2.sql (parents only)
                TRUNCATE TABLE $sp_core_product_vitals_table_name;

                INSERT INTO $sp_core_product_vitals_table_name (
                    `image_url`,
                    `name`,
                    `product_id`,
                    `parent_id`,
                    `parent_product_id`,
                    `category_ids`,
                    `product_creation_date`,
                    `product_age`,
                    `product_first_sale_date`,
                    `product_latest_sale_date`,
                    `parent_name`,
                    `parent_sku`,
                    `category_id`,
                    `brand_id`,
                    `brand`,
                    `supplier_id`,
                    `supplier`,
                    `menu_id`,
                    `menu_category`,
                    `head_id`,
                    `head_category`,
                    `subcat_id`,
                    `sub_category`,
                    `reference_quantity`,
                    `brand_bias`,
                    `campaigns`,
                    `season_start`,
                    `season_end`,
                    `season_midpoint`,
                    `terminated_product`,
                    `replacement_product`,
                    `sku`,
                    `gtin`,
                    `soft1_id`,
                    `profdiscount`,
                    `price`,
                    `regular_price`,
                    `cost`,
                    `onsale`,
                    `status`,
                    `product_url`,
                    `link_to_view`,
                    `link_to_cart`,
                    `link_to_fav`,
                    `short_description`,
                    `weight`,
                    `length`,
                    `width`,
                    `height`,
                    `color`,
                    `stock`,
                    `google_product_category`,
                    `facebook_product_category`
                )
                SELECT
                    CONCAT('$domain_name/wp-content/uploads/', pm2.meta_value) AS image_url,
                    CAST(p.post_title AS CHAR) AS name,
                    p.ID AS product_id,
                    p.ID AS parent_id,
                    p.ID AS parent_product_id,
                    tapc.category_ids AS category_ids,
                    CAST(COALESCE(p.post_date, '2022-01-01 00:00:00') AS DATETIME) AS product_creation_date,
                    DATEDIFF(CURDATE(), p.post_date) AS product_age,
                    CAST(COALESCE(MIN(o.date_created_gmt), NULL) AS DATETIME) AS product_first_sale_date,
                    CAST(COALESCE(MAX(o.date_created_gmt), NULL) AS DATETIME) AS product_latest_sale_date,
                    CAST(p.post_title AS CHAR) AS parent_name,
                    CAST(pm4.meta_value AS CHAR) AS parent_sku,
                    tapc.top_category_id AS category_id,
                    tat.brand_id AS brand_id,
                    CAST(tat.name AS CHAR) AS brand,
                    NULL AS supplier_id,  -- Supplier logic removed
                    NULL AS supplier,
                    tapc.top_category_id AS menu_id,
                    CAST(tapc.top_category AS CHAR) AS menu_category,
                    tapc.second_level_category_id AS head_id,
                    CAST(tapc.second_level_category AS CHAR) AS head_category,
                    tapc.third_level_category_id AS subcat_id,
                    CAST(tapc.third_level_category AS CHAR) AS sub_category,
                    COALESCE(CAST(NULLIF(pm11.meta_value, '') AS DECIMAL(12,0)), tapc.reference_quantity) AS reference_quantity,
                    IFNULL(tat.brand_bias, '0.5') AS brand_bias,
                    CONCAT_WS(', ',
                      NULLIF(GROUP_CONCAT(DISTINCT CAST(NULLIF(tat.campaigns,  '') AS CHAR) SEPARATOR ', '), ''),
                      NULLIF(GROUP_CONCAT(DISTINCT CAST(NULLIF(tapc.campaigns, '') AS CHAR) SEPARATOR ', '), '')
                    ) AS campaigns,
                    tapc.season_start AS season_start,
                    tapc.season_end AS season_end,
                    tapc.season_midpoint AS season_midpoint,
                    pm7.meta_value AS terminated_product,
                    pm8.meta_value AS replacement_product,
                    CAST(pm4.meta_value AS CHAR) AS sku,
                    CAST(pm0.meta_value AS CHAR) AS gtin,
                    CAST(pm9.meta_value AS CHAR) AS soft1_id,
                    CASE WHEN pm6.meta_value IS NOT NULL THEN 'NAI' ELSE 'OXI' END AS ProfDiscount,
                    CAST(REPLACE(COALESCE(NULLIF(pm3.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2)) AS price,
                    CAST(REPLACE(COALESCE(NULLIF(pm13.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2)) AS regular_price,
                    CAST(REPLACE(COALESCE(NULLIF(pm12.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2)) AS cost,
                    CASE
                        WHEN CAST(REPLACE(COALESCE(NULLIF(pm13.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2)) > CAST(REPLACE(COALESCE(NULLIF(pm3.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2))
                        THEN 1
                        ELSE 0
                    END AS onsale,
                    CASE WHEN p.post_status = 'publish' THEN 1 ELSE 0 END AS status,
                    CONCAT('$domain_name/?p=', p.ID) AS product_url,
                    CONCAT('<a href=$domain_name/?p=', p.ID, '>', 'Αναλυτική περιγραφή', '</a>') AS link_to_view,
                    CASE
                      WHEN pm5.meta_value > 0 THEN CONCAT(
                        '<div class=\"loop-button-wrap button-layout2\">',
                        '<a href=\"?add-to-cart=', p.ID,
                        '\" data-quantity=\"1\" class=\"button product_type_simple add_to_cart_button ajax_add_to_cart\" ',
                        'data-product_id=\"', p.ID, '\" data-product_sku=\"', CAST(pm4.meta_value AS CHAR),
                        '\" rel=\"nofollow\">',
                        'Αγορά',
                        '</a></div>'
                      )
                      ELSE ''
                    END AS link_to_cart,
                    '' AS link_to_fav,
                    LEFT(p.post_excerpt, 160) AS short_description,
                    CAST(NULLIF(pm10.meta_value, '') AS DECIMAL(10,2)) AS weight,
                    CAST(NULLIF(pm14.meta_value, '') AS DECIMAL(10,2)) AS length,
                    CAST(NULLIF(pm15.meta_value, '') AS DECIMAL(10,2)) AS width,
                    CAST(NULLIF(pm16.meta_value, '') AS DECIMAL(10,2)) AS height,
                    '' AS color,
                    CAST(NULLIF(pm5.meta_value, '') AS SIGNED) AS stock,
                    tapc.google_product_category   AS google_product_category,
                    tapc.facebook_product_category AS facebook_product_category
                FROM " . $sp_prefix . "posts AS p
                LEFT JOIN $sp_core_product_category_hierarchy_table_name AS tapc
                       ON p.ID = tapc.product_id
                LEFT JOIN " . $sp_prefix . "postmeta AS pm0
                       ON p.ID = pm0.post_id AND pm0.meta_key = '_global_unique_id'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm1
                       ON p.ID = pm1.post_id AND pm1.meta_key = '_thumbnail_id'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm2
                       ON pm1.meta_value = pm2.post_id AND pm2.meta_key = '_wp_attached_file'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm3
                       ON p.ID = pm3.post_id AND pm3.meta_key = '_price'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm4
                       ON p.ID = pm4.post_id AND pm4.meta_key = '_sku'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm5
                       ON p.ID = pm5.post_id AND pm5.meta_key = '_stock'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm6
                       ON p.ID = pm6.post_id AND pm6.meta_key = 'woocommerce_ultimate_pricing_prices' AND pm6.meta_value LIKE '%customer_%'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm7
                       ON p.ID = pm7.post_id AND pm7.meta_key = 'terminated_product'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm8
                       ON p.ID = pm8.post_id AND pm8.meta_key = 'replaced_by'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm9
                       ON p.ID = pm9.post_id AND pm9.meta_key = 'soft1_id'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm10
                       ON p.ID = pm10.post_id AND pm10.meta_key = '_weight'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm11
                       ON p.ID = pm11.post_id AND pm11.meta_key = 'reference_quantity'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm12
                       ON p.ID = pm12.post_id AND pm12.meta_key = '_cogs_total_value'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm13
                       ON p.ID = pm13.post_id AND pm13.meta_key = '_regular_price'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm14
                       ON p.ID = pm14.post_id AND pm14.meta_key = '_length'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm15
                       ON p.ID = pm15.post_id AND pm15.meta_key = '_width'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm16
                       ON p.ID = pm16.post_id AND pm16.meta_key = '_height'
                LEFT JOIN $sp_core_product_attributes_table_name AS tat
                       ON p.ID = tat.product_id
                LEFT JOIN " . $sp_prefix . "wc_order_product_lookup AS opl
                       ON p.ID = opl.product_id
                LEFT JOIN " . $sp_prefix . "wc_orders AS o
                       ON opl.order_id = o.id
                WHERE p.post_type = 'product'
                GROUP BY p.ID
                ORDER BY p.ID, tapc.top_category
                LIMIT 5000000;
            END";

    aieo_drop_sp_AIEO_InsertProductVitals($sp_prefix);
    $wpdb->query($sql);
}

function aieo_create_sp_AIEO_InsertVariationVitals($sp_prefix, $sp_core_variation_vitals_table_name)
{
    global $wpdb;

    $procedure_name = $sp_prefix . "AIEO_InsertVariationVitals";

    $sql = "CREATE PROCEDURE $procedure_name()
            BEGIN
                -- Simplified variation vitals logic WITHOUT slow terms JOINs
                -- This version removes cartesian explosion from terms/taxonomy joins
                TRUNCATE TABLE $sp_core_variation_vitals_table_name;

                INSERT INTO $sp_core_variation_vitals_table_name (
                  name,
                  product_id,
                  parent_id,
                  product_creation_date,
                  product_age,
                  product_first_sale_date,
                  product_latest_sale_date,
                  sku,
                  gtin,
                  price,
                  stock,
                  special_price,
                  weight,
                  length,
                  width,
                  height,
                  size,
                  color,
                  imagepath,
                  soft1_id,
                  erp_id,
                  regular_price,
                  onsale,
                  role_special_pricing,
                  variation_attribute_description,
                  variation_swatch,
                  variation_selector,
                  cost,
                  content_created,
                  content_last_updated,
                  ProdIdGraphUUID
                )
                SELECT
                    p.post_title AS name,
                    p.ID AS product_id,
                    p.post_parent AS parent_id,
                    CAST(COALESCE(p.post_date, '2022-01-01 00:00:00') AS DATETIME) AS product_creation_date,
                    DATEDIFF(CURDATE(), p.post_date) AS product_age,
                    NULL AS product_first_sale_date,
                    NULL AS product_latest_sale_date,
                    CAST(pm_sku.meta_value AS CHAR) AS sku,
                    CAST(pm_gtin.meta_value AS CHAR) AS gtin,
                    CAST(REPLACE(COALESCE(NULLIF(pm_price.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2)) AS price,
                    CAST(NULLIF(pm_stock.meta_value, '') AS SIGNED) AS stock,
                    CASE WHEN pm_special.meta_value IS NOT NULL THEN 'NAI' ELSE 'OXI' END AS special_price,
                    CAST(NULLIF(COALESCE(pm_weight.meta_value, parent_pm_weight.meta_value, ''), '') AS DECIMAL(10,2)) AS weight,
                    CAST(NULLIF(pm_length.meta_value, '') AS DECIMAL(10,2)) AS length,
                    CAST(NULLIF(pm_width.meta_value, '') AS DECIMAL(10,2)) AS width,
                    CAST(NULLIF(pm_height.meta_value, '') AS DECIMAL(10,2)) AS height,
                    pm_size.meta_value AS size,
                    -- color: resolved from the variation's COLOUR-FLAGGED attribute
                    -- (operator ticks 'AIEO colour context' on the attribute; the flag
                    -- lives in {$sp_prefix}aieo_dmm_color_attributes). We take the term
                    -- NAME (e.g. 'French Caramel'), NOT the slug, and only for flagged
                    -- attributes — so femme's pa_alezori-* image swatches now yield a
                    -- real colour. Correlated scalar subquery => exactly one value per
                    -- variation (no row fan-out). Empty when the variation has no
                    -- flagged colour attribute (flag-only, per operator: 'only real
                    -- colours'). This supersedes the old attribute_pa_%color% slug grab
                    -- (which matched nothing here) and the combine title-strip heuristic.
                    (
                        SELECT t_clr.name
                        FROM " . $sp_prefix . "postmeta AS pm_clr
                        INNER JOIN " . $sp_prefix . "aieo_dmm_color_attributes AS ca_clr
                            ON ca_clr.taxonomy = SUBSTRING(pm_clr.meta_key, CHAR_LENGTH('attribute_') + 1)
                        INNER JOIN " . $sp_prefix . "term_taxonomy AS tt_clr
                            ON tt_clr.taxonomy = ca_clr.taxonomy
                        INNER JOIN " . $sp_prefix . "terms AS t_clr
                            ON t_clr.term_id = tt_clr.term_id
                           AND t_clr.slug = pm_clr.meta_value
                        WHERE pm_clr.post_id = p.ID
                          AND pm_clr.meta_key LIKE 'attribute_%'
                          AND pm_clr.meta_value <> ''
                        ORDER BY t_clr.name
                        LIMIT 1
                    ) AS color,
                    -- imagepath precedence walks both WC variation-image fields:
                    --   1. _product_image_gallery (FIRST attachment id) — the WC 10.9
                    --      canonical key that AIEO migrated TO from
                    --      botiga_variation_gallery on 2026-05-23.
                    --   2. _thumbnail_id — the legacy / still-standard variation
                    --      featured-image key, used for the 8,458 variations not
                    --      yet picked up by the WC 10.9 migration.
                    -- The original SQL joined `_wp_attached_file` directly on the
                    -- variation post (always NULL — variations aren't attachments),
                    -- which is why every variation fell through to the 40x40 swatch
                    -- chip in the feed.
                    COALESCE(NULLIF(pm_pig_file.meta_value, ''), NULLIF(pm_thumb_file.meta_value, '')) AS imagepath,
                    CAST(pm_soft1.meta_value AS CHAR) AS soft1_id,
                    CAST(pm_soft1.meta_value AS CHAR) AS erp_id,
                    CAST(REPLACE(COALESCE(NULLIF(pm_regular.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2)) AS regular_price,
                    CASE
                        WHEN CAST(REPLACE(COALESCE(NULLIF(pm_regular.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2)) >
                             CAST(REPLACE(COALESCE(NULLIF(pm_price.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2))
                        THEN 1
                        ELSE 0
                    END AS onsale,
                    CASE WHEN pm_special.meta_value IS NOT NULL THEN 1 ELSE 0 END AS role_special_pricing,
                    '' AS variation_attribute_description,
                    '' AS variation_swatch,
                    0 AS variation_selector,
                    CAST(REPLACE(COALESCE(NULLIF(pm_cost.meta_value, ''), '0'), ',', '.') AS DECIMAL(12,2)) AS cost,
                    p.post_date_gmt AS content_created,
                    p.post_modified_gmt AS content_last_updated,
                    UNHEX(REPLACE(UUID(), '-', '')) AS ProdIdGraphUUID
                FROM " . $sp_prefix . "posts AS p
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_sku ON p.ID = pm_sku.post_id AND pm_sku.meta_key = '_sku'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_gtin ON p.ID = pm_gtin.post_id AND pm_gtin.meta_key = '_global_unique_id'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_price ON p.ID = pm_price.post_id AND pm_price.meta_key = '_price'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_stock ON p.ID = pm_stock.post_id AND pm_stock.meta_key = '_stock'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_weight ON p.ID = pm_weight.post_id AND pm_weight.meta_key = '_weight'
                LEFT JOIN " . $sp_prefix . "postmeta AS parent_pm_weight ON p.post_parent = parent_pm_weight.post_id AND parent_pm_weight.meta_key = '_weight'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_special ON p.ID = pm_special.post_id
                    AND pm_special.meta_key = 'woocommerce_ultimate_pricing_prices'
                    AND pm_special.meta_value LIKE '%customer_%'
                -- Variation image — two-hop via either the new WC 10.9 key
                -- (_product_image_gallery → first attachment ID) OR the legacy
                -- _thumbnail_id. Both lookups then resolve to _wp_attached_file
                -- on the attachment post. See the COALESCE in the SELECT above.
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_pig       ON pm_pig.post_id      = p.ID AND pm_pig.meta_key      = '_product_image_gallery'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_pig_file  ON pm_pig_file.post_id  = CAST(SUBSTRING_INDEX(NULLIF(pm_pig.meta_value, ''), ',', 1) AS UNSIGNED) AND pm_pig_file.meta_key  = '_wp_attached_file'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_thumb_id  ON pm_thumb_id.post_id  = p.ID AND pm_thumb_id.meta_key  = '_thumbnail_id'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_thumb_file ON pm_thumb_file.post_id = NULLIF(pm_thumb_id.meta_value, '') AND pm_thumb_file.meta_key = '_wp_attached_file'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_soft1 ON p.ID = pm_soft1.post_id AND pm_soft1.meta_key = 'iconic_cffv_100545_soft1_id'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_regular ON p.ID = pm_regular.post_id AND pm_regular.meta_key = '_regular_price'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_length ON p.ID = pm_length.post_id AND pm_length.meta_key = '_length'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_width ON p.ID = pm_width.post_id AND pm_width.meta_key = '_width'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_height ON p.ID = pm_height.post_id AND pm_height.meta_key = '_height'
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_size ON p.ID = pm_size.post_id AND pm_size.meta_key LIKE 'attribute_pa_%size%'
                -- (pm_color join removed: colour is now the flag-driven term-NAME
                --  subquery in the SELECT above, not an attribute_pa_%color% slug grab.)
                LEFT JOIN " . $sp_prefix . "postmeta AS pm_cost ON p.ID = pm_cost.post_id AND pm_cost.meta_key = '_cogs_total_value'
                WHERE p.post_type = 'product_variation'
                AND p.post_status = 'publish';
            END";

    aieo_drop_sp_AIEO_InsertVariationVitals($sp_prefix);
    $wpdb->query($sql);
}

function aieo_create_sp_AIEO_CombineVariationVitalsInProductVitals($sp_prefix, $sp_core_product_vitals_table_name, $domain_name)
{
    global $wpdb;

    
    $sp_core_product_attributes_table_name = get_option('aieo_core_product_attributes_table');
    $sp_core_product_category_hierarchy_table_name = get_option('aieo_core_product_category_hierarchy_table');
    $sp_core_variation_vitals_table_name = get_option('aieo_core_variation_vitals_table');

    $procedure_name = $sp_prefix . "AIEO_CombineVariationVitalsInProductVitals";

    $sql = "CREATE PROCEDURE $procedure_name()
            BEGIN
                -- Insert variations into product_vitals
                INSERT INTO $sp_core_product_vitals_table_name (
                    `image_url`,
                    `name`,
                    `product_id`,
                    `parent_id`,
                    `parent_product_id`,
                    `product_creation_date`,
                    `product_age`,
                    `product_first_sale_date`,
                    `product_latest_sale_date`,
                    `parent_name`,
                    `parent_sku`,
                    `category_id`,
                    `brand`,
                    `supplier_id`,
                    `supplier`,
                    `menu_id`,
                    `menu_category`,
                    `head_id`,
                    `head_category`,
                    `subcat_id`,
                    `sub_category`,
                    `reference_quantity`,
                    `brand_bias`,
                    `campaigns`,
                    `season_start`,
                    `season_end`,
                    `season_midpoint`,
                    `sku`,
                    `gtin`,
                    `soft1_id`,
                    `profdiscount`,
                    `price`,
                    `cost`,
                    `product_url`,
                    `link_to_view`,
                    `link_to_cart`,
                    `link_to_fav`,
                    `short_description`,
                    `weight`,
                    `color`,
                    `stock`,
                    `status`,
                    variation_selector,
                    variation_attribute_description,
                    variation_swatch,
                    `google_product_category`,
                    `facebook_product_category`
                )
                SELECT DISTINCT
                  -- Variation image precedence for feed exports:
                  --   1. variation's own _wp_attached_file (rare on variation posts)
                  --   2. PARENT product's featured image (full-size product photo —
                  --      this is the right fallback for the vast majority of variations
                  --      that don't carry their own image; e.g. Dalon Body Milk shades
                  --      all share the same bottle photo). Added 2026-05-26 to fix
                  --      feed pollution with 40x40 swatch chips.
                  --   3/4. swatch-map images (LAST resort — these are 40x40 chips, only
                  --      acceptable for marketplaces when the parent has no image at all).
                  -- Marketplaces (Skroutz/Bestprice/etc.) won't index a row with empty
                  -- <image>, so this fallback chain still gets every variation into
                  -- the feed — just with a SHOPPABLE-sized image now, not a swatch chip.
                  COALESCE(
                    NULLIF(CONCAT('$domain_name/wp-content/uploads/', vv.imagepath), '$domain_name/wp-content/uploads/'),
                    NULLIF(CONCAT('$domain_name/wp-content/uploads/', parent_thumb.meta_value), '$domain_name/wp-content/uploads/'),
                    NULLIF(svm.image_url, ''),
                    NULLIF(svm.swatch_image_url, '')
                  ) AS image_url,
                  CAST(p.post_title AS CHAR) AS name,
                  p.ID AS product_id,
                  vv.parent_id AS parent_id,
                  vv.parent_id AS parent_product_id,
                  CAST(COALESCE(vv.product_creation_date, '2022-01-01 00:00:00') AS DATETIME) AS product_creation_date,
                  vv.product_age AS product_age,
                  CAST(COALESCE(MAX(vv.product_first_sale_date),  NULL) AS DATETIME) AS product_first_sale_date,
                  CAST(COALESCE(MAX(vv.product_latest_sale_date), NULL) AS DATETIME) AS product_latest_sale_date,
                  CAST(pmmpp.post_title AS CHAR) AS parent_name,
                  CAST(pmmpps.meta_value AS CHAR) AS parent_sku,
                  tapcv.top_category_id AS category_id,
                  CAST(tatv.name AS CHAR) AS brand,
                  NULL AS supplier_id,  -- Supplier logic removed
                  NULL AS supplier,
                  tapcv.top_category_id AS menu_id,
                  CAST(tapcv.top_category AS CHAR) AS menu_category,
                  tapcv.second_level_category_id AS head_id,
                  CAST(tapcv.second_level_category AS CHAR) AS head_category,
                  tapcv.third_level_category_id AS subcat_id,
                  CAST(tapcv.third_level_category AS CHAR) AS sub_category,
                  tapcv.reference_quantity AS reference_quantity,
                  IFNULL(tatv.brand_bias, '0.5') AS brand_bias,
                  CONCAT_WS(', ',
                    NULLIF(GROUP_CONCAT(DISTINCT CAST(NULLIF(tatv.campaigns,  '') AS CHAR) SEPARATOR ', '), ''),
                    NULLIF(GROUP_CONCAT(DISTINCT CAST(NULLIF(tapcv.campaigns, '') AS CHAR) SEPARATOR ', '), '')
                  ) AS campaigns,
                  tapcv.season_start AS season_start,
                  tapcv.season_end AS season_end,
                  tapcv.season_midpoint AS season_midpoint,
                  CAST(vv.sku AS CHAR) AS sku,
                  CAST(vv.gtin AS CHAR) AS gtin,
                  CAST(vv.soft1_id AS CHAR) AS soft1_id,
                  vv.special_price AS ProfDiscount,
                  CAST(REPLACE(vv.price, ',', '.') AS DECIMAL(12,2)) AS price,
                  CAST(REPLACE(vv.cost,  ',', '.') AS DECIMAL(12,2)) AS cost,
                  CONCAT('$domain_name/?p=',  p.post_parent) AS product_url,
                  CONCAT('<a href=$domain_name/?p=', vv.parent_id, '>', 'Αναλυτική περιγραφή', '</a>') AS link_to_view,
                  CASE WHEN p.post_parent <> 0 AND vv.stock > 0 THEN
                    CONCAT(
                      '<div class=\"loop-button-wrap button-layout2\"><a href=\"?add-to-cart=', p.id,
                      '\" data-quantity=\"1\" class=\"button product_type_simple add_to_cart_button ajax_add_to_cart\" ',
                      'data-product_id=\"', p.id, '\" data-product_sku=\"', CAST(vv.sku AS CHAR), '\" rel=\"nofollow\">',
                      'Αγορά',
                      '</a></div>'
                    )
                    ELSE ''
                  END AS link_to_cart,
                  '' AS link_to_fav,
                  LEFT(pmmpp.post_excerpt, 160) AS short_description,
                  vv.weight AS weight,
                  -- colour now comes straight from variation_vitals (the flag-driven
                  -- term NAME populated by AIEO_InsertVariationVitals), NOT the old
                  -- title-minus-parent strip. Empty when the variation has no
                  -- COLOUR-flagged attribute. Parents keep NULL (Google puts colour on
                  -- each variant + variesBy:color on the group, never on the parent).
                  vv.color AS color,
                  vv.stock AS stock,
                  CASE WHEN pmmpp.post_status = 'publish' THEN 1 ELSE 0 END AS status,
                  vv.variation_selector AS variation_selector,
                  vv.variation_attribute_description AS variation_attribute_description,
                  vv.variation_swatch AS variation_swatch,
                  -- Variations inherit the parent's marketplace-taxonomy
                  -- mapping from wp_aieo_core_product_category_hierarchy
                  -- (joined as tapcv, keyed on post_parent above). Same
                  -- COALESCE chain InsertProductVitals already resolved.
                  tapcv.google_product_category   AS google_product_category,
                  tapcv.facebook_product_category AS facebook_product_category
                FROM `" . $sp_prefix . "posts` AS p
                LEFT JOIN " . $sp_prefix . "posts pmmp                 ON pmmp.ID            = p.id
                LEFT JOIN " . $sp_prefix . "posts pmmpp                ON pmmpp.ID           = pmmp.post_parent
                LEFT JOIN `" . $sp_prefix . "postmeta` pmmpps          ON pmmpp.ID           = pmmpps.post_id AND pmmpps.meta_key = '_sku'
                LEFT JOIN `$sp_core_variation_vitals_table_name` vv ON p.id               = vv.product_id
                LEFT JOIN `$sp_core_product_attributes_table_name`  tatv ON p.ID              = tatv.product_id
                LEFT JOIN `$sp_core_product_category_hierarchy_table_name` tapcv ON p.post_parent = tapcv.product_id
                LEFT JOIN `" . $sp_prefix . "aieo_dmm_swatches_variation_map` svm ON svm.variation_id = p.ID
                -- Parent product's featured image — two-hop postmeta walk:
                -- variation's parent → _thumbnail_id → attachment → _wp_attached_file.
                -- Used by the COALESCE image precedence above as the fallback when
                -- the variation has no own imagepath, so we get the FULL product photo
                -- in the feed instead of a 40x40 swatch chip.
                LEFT JOIN `" . $sp_prefix . "postmeta` parent_thumb_id ON parent_thumb_id.post_id = p.post_parent AND parent_thumb_id.meta_key = '_thumbnail_id'
                LEFT JOIN `" . $sp_prefix . "postmeta` parent_thumb    ON parent_thumb.post_id    = parent_thumb_id.meta_value AND parent_thumb.meta_key    = '_wp_attached_file'
                WHERE p.post_type IN ('product_variation')
                  AND p.post_status = 'publish'
                GROUP BY p.ID
                ORDER BY p.id, tapcv.top_category;

                -- Mark parents that have at least one variation
                UPDATE $sp_core_product_vitals_table_name p
                LEFT JOIN (
                  SELECT DISTINCT parent_id
                  FROM $sp_core_variation_vitals_table_name
                  WHERE parent_id IS NOT NULL AND parent_id <> 0
                ) v ON v.parent_id = p.product_id
                SET p.is_variation_parent = CASE WHEN v.parent_id IS NULL THEN 0 ELSE 1 END
                WHERE p.parent_id = p.product_id;

                -- Update parent stock and variation counts
                UPDATE $sp_core_product_vitals_table_name AS parent
                JOIN (
                    SELECT
                        parent_id,
                        SUM(stock) AS sum_variation_stock,
                        SUM(CASE WHEN stock > 0 THEN 1 ELSE 0 END) AS variations_in_stock
                    FROM $sp_core_product_vitals_table_name
                    WHERE parent_id <> product_id
                    GROUP BY parent_id
                ) AS childSum
                    ON parent.product_id = childSum.parent_id
                SET
                    parent.stock = childSum.sum_variation_stock,
                    parent.variation_count = childSum.variations_in_stock;

                -- Point product_url at the SEO canonical when one exists, ABSOLUTISED
                -- with the site origin. The materialised canonical_url is stored as a
                -- portable site-RELATIVE path (/product/...); product_url is an ABSOLUTE
                -- URL by contract (the ?p=ID seed above is absolute), and the marketplace
                -- feeds + any external consumer need a full link. Mirror the sitemap's
                -- rule (build_urlset): already-absolute canonicals (operator overrides)
                -- pass through unchanged, relative ones get the $domain_name prefix.
                -- INNER JOIN + the WHERE leave rows WITHOUT a canonical (or whose FINAL
                -- absolute URL exceeds 300) untouched, so they keep their ?p=ID reference.
                -- LEFT(...,300) is a final hard cap so a stray long URL can never overflow
                -- product_url and fail this build. Variations inherit via parent_product_id.
                UPDATE $sp_core_product_vitals_table_name v
                INNER JOIN " . $sp_prefix . "aieo_seo_posts sp
                        ON sp.object_type = 'product'
                       AND sp.object_id   = v.parent_product_id
                SET v.product_url = LEFT(
                        CASE
                            WHEN sp.canonical_url LIKE 'http://%' OR sp.canonical_url LIKE 'https://%'
                                THEN sp.canonical_url
                            ELSE CONCAT('$domain_name', '/', TRIM(LEADING '/' FROM sp.canonical_url))
                        END, 300)
                WHERE sp.canonical_url IS NOT NULL
                  AND sp.canonical_url <> ''
                  AND CHAR_LENGTH(
                        CASE
                            WHEN sp.canonical_url LIKE 'http://%' OR sp.canonical_url LIKE 'https://%'
                                THEN sp.canonical_url
                            ELSE CONCAT('$domain_name', '/', TRIM(LEADING '/' FROM sp.canonical_url))
                        END
                      ) <= 300;

                -- Set swatches offset and in_stock_variation_offset
                SET @swatches_offset = 5;

                UPDATE $sp_core_product_vitals_table_name AS parent
                JOIN (
                    SELECT
                        parent_id,
                        SUM(stock) AS sum_variation_stock,
                        SUM(CASE WHEN stock > 0 THEN 1 ELSE 0 END) AS variations_in_stock
                    FROM $sp_core_product_vitals_table_name
                    WHERE parent_id <> product_id
                    GROUP BY parent_id
                ) AS childSum
                    ON parent.product_id = childSum.parent_id
                SET
                    parent.stock = childSum.sum_variation_stock,
                    parent.variation_count = childSum.variations_in_stock,
                    parent.in_stock_variation_offset = CASE
                        WHEN childSum.variations_in_stock > @swatches_offset
                            THEN CONCAT('+', childSum.variations_in_stock - @swatches_offset)
                        ELSE ''
                    END;

                -- Per-parent flat index of every variation's SKU / barcode(gtin),
                -- so the storefront reads ONE flat vitals row instead of joining
                -- or querying WooCommerce per request. Computed once here in the
                -- (hourly) build — replaces the legacy pods sku_index/barcode_index
                -- that generatefastfinderv2 used to build.
                SET SESSION group_concat_max_len = 1000000;
                UPDATE $sp_core_product_vitals_table_name AS parent
                JOIN (
                    SELECT
                        parent_id,
                        GROUP_CONCAT(NULLIF(sku, '')  ORDER BY product_id SEPARATOR ',') AS sku_idx,
                        GROUP_CONCAT(NULLIF(gtin, '') ORDER BY product_id SEPARATOR ',') AS barcode_idx
                    FROM $sp_core_product_vitals_table_name
                    WHERE parent_id <> product_id
                    GROUP BY parent_id
                ) AS childIdx
                    ON parent.product_id = childIdx.parent_id
                SET
                    parent.sku_index     = childIdx.sku_idx,
                    parent.barcode_index = childIdx.barcode_idx;
            END";

    aieo_drop_sp_AIEO_CombineVariationVitalsInProductVitals($sp_prefix);
    $wpdb->query($sql);
}

function aieo_drop_sp_AIEO_InsertCoreProductAttributes($sp_prefix)
{
    aieo_drop_sp_common($sp_prefix, "AIEO_InsertCoreProductAttributes");
}

function aieo_drop_sp_AIEO_InsertProductCategoryHierarchy($sp_prefix)
{
    aieo_drop_sp_common($sp_prefix, "AIEO_InsertProductCategoryHierarchy");
}

function aieo_drop_sp_AIEO_InsertProductVitals($sp_prefix)
{
    aieo_drop_sp_common($sp_prefix, "AIEO_InsertProductVitals");
}

function aieo_drop_sp_AIEO_InsertVariationVitals($sp_prefix)
{
    aieo_drop_sp_common($sp_prefix, "AIEO_InsertVariationVitals");
}

function aieo_drop_sp_AIEO_CombineVariationVitalsInProductVitals($sp_prefix)
{
    aieo_drop_sp_common($sp_prefix, "AIEO_CombineVariationVitalsInProductVitals");
}

function aieo_create_sp_AIEO_UpdateCustomerCentricStdStats_v2($sp_prefix, $temp_orders_table_name)
{
    global $wpdb;

    $procedure_name = $sp_prefix . "AIEO_UpdateCustomerCentricStdStats_v2";
    $base_order_stats_table = $wpdb->prefix . 'aieo_base_order_stats';
    $cumulative_stats_table = $wpdb->prefix . 'aieo_cumulative_customer_stats';

    $sql = "CREATE PROCEDURE $procedure_name(
                IN target_table_name VARCHAR(255),
                IN TargetDate INT
            )
            BEGIN
                -- STEP 1: Create base order stats table (one row per order)
                -- Similar to wp_pods_order_stats from cronjobs
                DROP TABLE IF EXISTS $base_order_stats_table;

                CREATE TABLE $base_order_stats_table (
                    `EponymousId` INT NOT NULL,
                    `OrderId` BIGINT NOT NULL,
                    `OrderSpend` DECIMAL(12,2),
                    `OrderSize` INT,
                    `NumberOfOrders` INT,
                    INDEX idx_eponymous (EponymousId),
                    INDEX idx_order (OrderId)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

                -- Populate base order stats - one row per order with aggregated order-level data
                -- Using dynamic SQL to handle table name parameter
                SET @sql1 = CONCAT('
                    INSERT INTO $base_order_stats_table (EponymousId, OrderId, OrderSpend, OrderSize, NumberOfOrders)
                    SELECT
                        EponymousId,
                        OrderId,
                        MAX(OrderItemsTotal) as OrderSpend,
                        MAX(OrderSize) as OrderSize,
                        (SELECT COUNT(DISTINCT o2.OrderId)
                         FROM ', target_table_name, ' o2
                         WHERE o2.EponymousId = o1.EponymousId
                         AND o2.EponymousId <> 0) as NumberOfOrders
                    FROM ', target_table_name, ' o1
                    WHERE EponymousId <> 0
                    GROUP BY EponymousId, OrderId
                ');
                PREPARE stmt1 FROM @sql1;
                EXECUTE stmt1;
                DEALLOCATE PREPARE stmt1;

                -- STEP 2: Create cumulative stats table via self-join
                -- This is the key optimization from generatecumorderstats.sql
                DROP TABLE IF EXISTS $cumulative_stats_table;

                CREATE TABLE $cumulative_stats_table (
                    INDEX idx_eponymous_order (EponymousId, OrderId)
                )
                SELECT
                    ws1.OrderId,
                    ws1.EponymousId,
                    ws1.OrderSpend,
                    ws1.OrderSize,
                    ws1.NumberOfOrders,
                    ROUND(ws1.OrderSpend / NULLIF(ws1.OrderSize, 0), 2) as AveSpendPerItemPerOrder,
                    ROUND(AVG(ws2.OrderSpend), 2) as TotalAverageOrder,
                    ROUND(AVG(ws2.OrderSize), 2) as AveItemsPerOrder,
                    ROUND(STD(ws2.OrderSize), 2) as STDItemsOfUser,
                    ROUND(STD(ws2.OrderSize) / NULLIF(MIN(ws2.OrderSize), 0), 2) as STDPerMinItemsOfUser,
                    ROUND(STD(ws2.OrderSize) / NULLIF(AVG(ws2.OrderSize), 0), 2) as STDPerAverageItemsOfUser,
                    ROUND(STD(ws2.OrderSize) / NULLIF(MAX(ws2.OrderSize), 0), 2) as STDPerMaxItemsOfUser,
                    ROUND(AVG(ws2.OrderSpend), 2) as AveSpendPerOrder,
                    ROUND(STD(ws2.OrderSpend), 2) as STDSpendOfUser,
                    ROUND(STD(ws2.OrderSpend) / NULLIF(MIN(ws2.OrderSpend), 0), 2) as STDPerMinSpendOfUser,
                    ROUND(STD(ws2.OrderSpend) / NULLIF(AVG(ws2.OrderSpend), 0), 2) as STDPerAverageSpendOfUser,
                    ROUND(STD(ws2.OrderSpend) / NULLIF(MAX(ws2.OrderSpend), 0), 2) as STDPerMaxSpendOfUser
                FROM $base_order_stats_table ws1
                JOIN $base_order_stats_table ws2 ON ws1.EponymousId = ws2.EponymousId
                GROUP BY ws1.EponymousId, ws1.OrderId, ws1.OrderSize, ws1.NumberOfOrders, ws1.OrderSpend
                ORDER BY ws1.EponymousId;

                -- Add primary key after table creation for better performance
                ALTER TABLE $cumulative_stats_table
                ADD COLUMN `id` BIGINT(45) UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
                ADD PRIMARY KEY (`id`);

                -- STEP 3: Update the temp orders table with the 6 basic statistics
                -- These are the ONLY columns that exist in temp_orders (from original procedure)
                SET @sql2 = CONCAT('
                    UPDATE ', target_table_name, ' AS t1
                    INNER JOIN (
                        SELECT
                            EponymousId,
                            AVG(OrderSize) AS AvgOrderSize,
                            MAX(OrderSize) AS MaxOrderSize,
                            MIN(OrderSize) AS MinOrderSize,
                            AVG(OrderSpend) AS AvgSpend,
                            MAX(OrderSpend) AS MaxSpend,
                            MIN(OrderSpend) AS MinSpend
                        FROM $base_order_stats_table
                        WHERE EponymousId <> 0
                        GROUP BY EponymousId
                    ) AS cs ON t1.EponymousId = cs.EponymousId
                    SET
                        t1.AvgOrderSize = cs.AvgOrderSize,
                        t1.MaxOrderSize = cs.MaxOrderSize,
                        t1.MinOrderSize = cs.MinOrderSize,
                        t1.AvgSpend = cs.AvgSpend,
                        t1.MaxSpend = cs.MaxSpend,
                        t1.MinSpend = cs.MinSpend
                ');
                PREPARE stmt2 FROM @sql2;
                EXECUTE stmt2;
                DEALLOCATE PREPARE stmt2;

                -- STEP 4: Update total customer statistics
                SET @sql3 = CONCAT('
                    UPDATE ', target_table_name, ' AS t1
                    INNER JOIN (
                        SELECT
                            EponymousId,
                            SUM(PrimaryQty) AS TotalCustomerItems,
                            COUNT(DISTINCT OrderId) AS TotalCustomerOrders,
                            SUM(LineValue) AS TotalCustomerTurnover
                        FROM ', target_table_name, '
                        WHERE EponymousId <> 0
                        GROUP BY EponymousId
                    ) AS cs ON t1.EponymousId = cs.EponymousId
                    SET
                        t1.TotalCustomerItems = cs.TotalCustomerItems,
                        t1.TotalCustomerOrders = cs.TotalCustomerOrders,
                        t1.TotalCustomerTurnover = cs.TotalCustomerTurnover
                ');
                PREPARE stmt3 FROM @sql3;
                EXECUTE stmt3;
                DEALLOCATE PREPARE stmt3;

            END";

    aieo_drop_sp_AIEO_UpdateCustomerCentricStdStats_v2($sp_prefix);
    $wpdb->query($sql);
}

function aieo_drop_sp_AIEO_UpdateCustomerCentricStdStats_v2($sp_prefix)
{
    aieo_drop_sp_common($sp_prefix, "AIEO_UpdateCustomerCentricStdStats_v2");
}

function aieo_create_sp_AIEO_UpdateCustomerCentricStdStats_Smart($sp_prefix, $temp_orders_table_name)
{
    global $wpdb;

    $procedure_name = $sp_prefix . "AIEO_UpdateCustomerCentricStdStats";
    $base_order_stats_table = $wpdb->prefix . 'aieo_base_order_stats';
    $cumulative_stats_table = $wpdb->prefix . 'aieo_cumulative_customer_stats';

    $sql = "CREATE PROCEDURE $procedure_name(
                IN target_table_name VARCHAR(255),
                IN TargetDate INT,
                IN batch_offset INT,
                IN batch_size INT
            )
            BEGIN
                -- If batch_offset = 0, we're starting - create the base tables
                IF batch_offset = 0 THEN
                    -- STEP 1: Create base order stats table
                    DROP TABLE IF EXISTS $base_order_stats_table;

                    CREATE TABLE $base_order_stats_table (
                        `EponymousId` INT NOT NULL,
                        `OrderId` BIGINT NOT NULL,
                        `OrderSpend` DECIMAL(12,2),
                        `OrderSize` INT,
                        `NumberOfOrders` INT,
                        INDEX idx_eponymous (EponymousId),
                        INDEX idx_order (OrderId)
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

                    SET @sql1 = CONCAT('
                        INSERT INTO $base_order_stats_table (EponymousId, OrderId, OrderSpend, OrderSize, NumberOfOrders)
                        SELECT
                            EponymousId,
                            OrderId,
                            MAX(OrderItemsTotal) as OrderSpend,
                            MAX(OrderSize) as OrderSize,
                            (SELECT COUNT(DISTINCT o2.OrderId)
                             FROM ', target_table_name, ' o2
                             WHERE o2.EponymousId = o1.EponymousId
                             AND o2.EponymousId <> 0) as NumberOfOrders
                        FROM ', target_table_name, ' o1
                        WHERE EponymousId <> 0
                        GROUP BY EponymousId, OrderId
                    ');
                    PREPARE stmt1 FROM @sql1;
                    EXECUTE stmt1;
                    DEALLOCATE PREPARE stmt1;

                    -- STEP 2: Create cumulative stats table (if needed - optional for basic stats)
                    -- Skipped for now to keep it simple - only basic stats
                END IF;

                -- STEP 3: Update customers in this batch (ID range based on batch_offset and batch_size)
                -- First, create a temp table with the customer IDs for this batch (MariaDB workaround for LIMIT in subquery)
                DROP TEMPORARY TABLE IF EXISTS temp_batch_customers;
                SET @sql_temp = CONCAT('
                    CREATE TEMPORARY TABLE temp_batch_customers AS
                    SELECT DISTINCT EponymousId
                    FROM $base_order_stats_table
                    WHERE EponymousId <> 0
                    ORDER BY EponymousId
                    LIMIT ', batch_size, ' OFFSET ', batch_offset, '
                ');
                PREPARE stmt_temp FROM @sql_temp;
                EXECUTE stmt_temp;
                DEALLOCATE PREPARE stmt_temp;

                -- Now update using the temp table
                SET @sql2 = CONCAT('
                    UPDATE ', target_table_name, ' AS t1
                    INNER JOIN (
                        SELECT
                            EponymousId,
                            AVG(OrderSize) AS AvgOrderSize,
                            MAX(OrderSize) AS MaxOrderSize,
                            MIN(OrderSize) AS MinOrderSize,
                            AVG(OrderSpend) AS AvgSpend,
                            MAX(OrderSpend) AS MaxSpend,
                            MIN(OrderSpend) AS MinSpend
                        FROM $base_order_stats_table
                        WHERE EponymousId <> 0
                        AND EponymousId IN (SELECT EponymousId FROM temp_batch_customers)
                        GROUP BY EponymousId
                    ) AS cs ON t1.EponymousId = cs.EponymousId
                    SET
                        t1.AvgOrderSize = cs.AvgOrderSize,
                        t1.MaxOrderSize = cs.MaxOrderSize,
                        t1.MinOrderSize = cs.MinOrderSize,
                        t1.AvgSpend = cs.AvgSpend,
                        t1.MaxSpend = cs.MaxSpend,
                        t1.MinSpend = cs.MinSpend
                ');
                PREPARE stmt2 FROM @sql2;
                EXECUTE stmt2;
                DEALLOCATE PREPARE stmt2;

                -- STEP 4: Update total customer statistics for this batch (reuse temp_batch_customers)
                SET @sql3 = CONCAT('
                    UPDATE ', target_table_name, ' AS t1
                    INNER JOIN (
                        SELECT
                            EponymousId,
                            SUM(PrimaryQty) AS TotalCustomerItems,
                            COUNT(DISTINCT OrderId) AS TotalCustomerOrders,
                            SUM(LineValue) AS TotalCustomerTurnover
                        FROM ', target_table_name, '
                        WHERE EponymousId <> 0
                        AND EponymousId IN (SELECT EponymousId FROM temp_batch_customers)
                        GROUP BY EponymousId
                    ) AS cs ON t1.EponymousId = cs.EponymousId
                    SET
                        t1.TotalCustomerItems = cs.TotalCustomerItems,
                        t1.TotalCustomerOrders = cs.TotalCustomerOrders,
                        t1.TotalCustomerTurnover = cs.TotalCustomerTurnover
                ');
                PREPARE stmt3 FROM @sql3;
                EXECUTE stmt3;
                DEALLOCATE PREPARE stmt3;

                -- ============================================================
                -- STEP 5: Top-3 favourites per customer (denormalized FavN
                -- columns on every line of temp_orders).
                --
                -- Rank dimensions (each independent — same shape, different
                -- source column):
                --   ProductId → Fav1Product / Fav2Product / Fav3Product
                --   BrandId   → Fav1Brand   / Fav2Brand   / Fav3Brand
                --   MenuId    → Fav1MainCat / Fav2MainCat / Fav3MainCat
                --   HeadId    → Fav1HeadCat / Fav2HeadCat / Fav3HeadCat
                --   SubcatId  → Fav1SubCat  / Fav2SubCat  / Fav3SubCat
                --
                -- For each (customer, dimension), GROUP BY (id, customer)
                -- summing LineValue → ROW_NUMBER() rank by total desc → pivot
                -- top-3 into 3 cols via MAX(IF(rnk=N, id, NULL)). Tie-break
                -- by id ASC so ordering is deterministic across re-runs.
                -- Scoped to the current batch via temp_batch_customers, so
                -- the cost stays linear in batch size regardless of total
                -- customer count.
                --
                -- Note: the catalog-hierarchy sproc only sets MenuId/HeadId
                -- when a product is directly tagged with a parent=0
                -- product_cat. Most FF products are tagged on leaf
                -- subcategories only, so coverage on those two columns is
                -- low (~10/170k lines). SubcatId has full coverage and is
                -- the most useful favourite-category signal until the
                -- upstream hierarchy walker is fixed.
                -- ============================================================

                -- Fav1Brand / Fav2Brand / Fav3Brand
                SET @sql_fav_brand = CONCAT('
                    UPDATE ', target_table_name, ' AS t1
                    INNER JOIN (
                        SELECT EponymousId,
                            MAX(IF(rnk = 1, BrandId, NULL)) AS f1,
                            MAX(IF(rnk = 2, BrandId, NULL)) AS f2,
                            MAX(IF(rnk = 3, BrandId, NULL)) AS f3
                        FROM (
                            SELECT EponymousId, BrandId,
                                ROW_NUMBER() OVER (
                                    PARTITION BY EponymousId
                                    ORDER BY total DESC, BrandId ASC
                                ) AS rnk
                            FROM (
                                SELECT EponymousId, BrandId,
                                    SUM(IFNULL(LineValue, 0)) AS total
                                FROM ', target_table_name, '
                                WHERE EponymousId <> 0
                                  AND EponymousId IN (SELECT EponymousId FROM temp_batch_customers)
                                  AND BrandId IS NOT NULL AND BrandId > 0
                                GROUP BY EponymousId, BrandId
                            ) totals
                        ) ranked
                        WHERE rnk <= 3
                        GROUP BY EponymousId
                    ) f ON f.EponymousId = t1.EponymousId
                    SET t1.Fav1Brand = f.f1,
                        t1.Fav2Brand = f.f2,
                        t1.Fav3Brand = f.f3
                ');
                PREPARE stmt_fav_brand FROM @sql_fav_brand;
                EXECUTE stmt_fav_brand;
                DEALLOCATE PREPARE stmt_fav_brand;

                -- Fav1Product / Fav2Product / Fav3Product
                SET @sql_fav_product = CONCAT('
                    UPDATE ', target_table_name, ' AS t1
                    INNER JOIN (
                        SELECT EponymousId,
                            MAX(IF(rnk = 1, ProductId, NULL)) AS f1,
                            MAX(IF(rnk = 2, ProductId, NULL)) AS f2,
                            MAX(IF(rnk = 3, ProductId, NULL)) AS f3
                        FROM (
                            SELECT EponymousId, ProductId,
                                ROW_NUMBER() OVER (
                                    PARTITION BY EponymousId
                                    ORDER BY total DESC, ProductId ASC
                                ) AS rnk
                            FROM (
                                SELECT EponymousId, ProductId,
                                    SUM(IFNULL(LineValue, 0)) AS total
                                FROM ', target_table_name, '
                                WHERE EponymousId <> 0
                                  AND EponymousId IN (SELECT EponymousId FROM temp_batch_customers)
                                  AND ProductId IS NOT NULL AND ProductId > 0
                                GROUP BY EponymousId, ProductId
                            ) totals
                        ) ranked
                        WHERE rnk <= 3
                        GROUP BY EponymousId
                    ) f ON f.EponymousId = t1.EponymousId
                    SET t1.Fav1Product = f.f1,
                        t1.Fav2Product = f.f2,
                        t1.Fav3Product = f.f3
                ');
                PREPARE stmt_fav_product FROM @sql_fav_product;
                EXECUTE stmt_fav_product;
                DEALLOCATE PREPARE stmt_fav_product;

                -- Fav1MainCat / Fav2MainCat / Fav3MainCat (MenuId source)
                SET @sql_fav_maincat = CONCAT('
                    UPDATE ', target_table_name, ' AS t1
                    INNER JOIN (
                        SELECT EponymousId,
                            MAX(IF(rnk = 1, MenuId, NULL)) AS f1,
                            MAX(IF(rnk = 2, MenuId, NULL)) AS f2,
                            MAX(IF(rnk = 3, MenuId, NULL)) AS f3
                        FROM (
                            SELECT EponymousId, MenuId,
                                ROW_NUMBER() OVER (
                                    PARTITION BY EponymousId
                                    ORDER BY total DESC, MenuId ASC
                                ) AS rnk
                            FROM (
                                SELECT EponymousId, MenuId,
                                    SUM(IFNULL(LineValue, 0)) AS total
                                FROM ', target_table_name, '
                                WHERE EponymousId <> 0
                                  AND EponymousId IN (SELECT EponymousId FROM temp_batch_customers)
                                  AND MenuId IS NOT NULL AND MenuId > 0
                                GROUP BY EponymousId, MenuId
                            ) totals
                        ) ranked
                        WHERE rnk <= 3
                        GROUP BY EponymousId
                    ) f ON f.EponymousId = t1.EponymousId
                    SET t1.Fav1MainCat = f.f1,
                        t1.Fav2MainCat = f.f2,
                        t1.Fav3MainCat = f.f3
                ');
                PREPARE stmt_fav_maincat FROM @sql_fav_maincat;
                EXECUTE stmt_fav_maincat;
                DEALLOCATE PREPARE stmt_fav_maincat;

                -- Fav1HeadCat / Fav2HeadCat / Fav3HeadCat (HeadId source)
                SET @sql_fav_headcat = CONCAT('
                    UPDATE ', target_table_name, ' AS t1
                    INNER JOIN (
                        SELECT EponymousId,
                            MAX(IF(rnk = 1, HeadId, NULL)) AS f1,
                            MAX(IF(rnk = 2, HeadId, NULL)) AS f2,
                            MAX(IF(rnk = 3, HeadId, NULL)) AS f3
                        FROM (
                            SELECT EponymousId, HeadId,
                                ROW_NUMBER() OVER (
                                    PARTITION BY EponymousId
                                    ORDER BY total DESC, HeadId ASC
                                ) AS rnk
                            FROM (
                                SELECT EponymousId, HeadId,
                                    SUM(IFNULL(LineValue, 0)) AS total
                                FROM ', target_table_name, '
                                WHERE EponymousId <> 0
                                  AND EponymousId IN (SELECT EponymousId FROM temp_batch_customers)
                                  AND HeadId IS NOT NULL AND HeadId > 0
                                GROUP BY EponymousId, HeadId
                            ) totals
                        ) ranked
                        WHERE rnk <= 3
                        GROUP BY EponymousId
                    ) f ON f.EponymousId = t1.EponymousId
                    SET t1.Fav1HeadCat = f.f1,
                        t1.Fav2HeadCat = f.f2,
                        t1.Fav3HeadCat = f.f3
                ');
                PREPARE stmt_fav_headcat FROM @sql_fav_headcat;
                EXECUTE stmt_fav_headcat;
                DEALLOCATE PREPARE stmt_fav_headcat;

                -- Fav1SubCat / Fav2SubCat / Fav3SubCat (SubcatId source)
                SET @sql_fav_subcat = CONCAT('
                    UPDATE ', target_table_name, ' AS t1
                    INNER JOIN (
                        SELECT EponymousId,
                            MAX(IF(rnk = 1, SubcatId, NULL)) AS f1,
                            MAX(IF(rnk = 2, SubcatId, NULL)) AS f2,
                            MAX(IF(rnk = 3, SubcatId, NULL)) AS f3
                        FROM (
                            SELECT EponymousId, SubcatId,
                                ROW_NUMBER() OVER (
                                    PARTITION BY EponymousId
                                    ORDER BY total DESC, SubcatId ASC
                                ) AS rnk
                            FROM (
                                SELECT EponymousId, SubcatId,
                                    SUM(IFNULL(LineValue, 0)) AS total
                                FROM ', target_table_name, '
                                WHERE EponymousId <> 0
                                  AND EponymousId IN (SELECT EponymousId FROM temp_batch_customers)
                                  AND SubcatId IS NOT NULL AND SubcatId > 0
                                GROUP BY EponymousId, SubcatId
                            ) totals
                        ) ranked
                        WHERE rnk <= 3
                        GROUP BY EponymousId
                    ) f ON f.EponymousId = t1.EponymousId
                    SET t1.Fav1SubCat = f.f1,
                        t1.Fav2SubCat = f.f2,
                        t1.Fav3SubCat = f.f3
                ');
                PREPARE stmt_fav_subcat FROM @sql_fav_subcat;
                EXECUTE stmt_fav_subcat;
                DEALLOCATE PREPARE stmt_fav_subcat;

                -- Clean up temp table
                DROP TEMPORARY TABLE IF EXISTS temp_batch_customers;

            END";

    aieo_drop_sp_AIEO_UpdateCustomerCentricStdStats($sp_prefix);
    $wpdb->query($sql);
}

function aieo_drop_sp_AIEO_UpdateCustomerCentricStdStats($sp_prefix)
{
    aieo_drop_sp_common($sp_prefix, "AIEO_UpdateCustomerCentricStdStats");
}

function aieo_create_sp_AIEO_UpdateCustomerCentricAdvStats($sp_prefix, $sp_orders_table)
{
    global $wpdb;

    $procedure_name = $sp_prefix . "AIEO_UpdateCustomerCentricAdvStats";

    $sql = "CREATE PROCEDURE $procedure_name(
                IN target_table_name VARCHAR(255),
                IN use_product_id TINYINT,
                IN batch_offset INT,
                IN batch_size INT
            )
            BEGIN
                DECLARE column_id VARCHAR(255);
                SET column_id = IF(use_product_id = 1, 'ProductId', 'ParentProductId');

                -- If batch_offset = 0, we're starting - create the persistent temp tables
                IF batch_offset = 0 THEN
                    -- Create a persistent table with a row number for ALL customers
                    -- Only select columns we need for advanced stats calculations
                    -- Using regular table (not TEMPORARY) so it persists across AJAX requests
                    DROP TABLE IF EXISTS wp_aieo_adv_stats_orders_rn;
                    SET @sql = CONCAT('
                        CREATE TABLE wp_aieo_adv_stats_orders_rn (
                            EponymousId INT,
                            OrderId BIGINT,
                            OrderDate DATETIME,
                            OrderItemsTotal DECIMAL(10,2),
                            rn INT,
                            INDEX idx_eponymous (EponymousId),
                            INDEX idx_eponymous_rn (EponymousId, rn)
                        ) ENGINE=InnoDB AS
                        SELECT EponymousId, OrderId, OrderDate, OrderItemsTotal,
                               ROW_NUMBER() OVER (PARTITION BY EponymousId ORDER BY OrderDate) AS rn
                        FROM ', target_table_name, '
                        WHERE EponymousId <> 0;
                    ');

                    PREPARE temp_stmt FROM @sql;
                    EXECUTE temp_stmt;
                    DEALLOCATE PREPARE temp_stmt;

                    -- Create a copy for self-join with same indexes
                    DROP TABLE IF EXISTS wp_aieo_adv_stats_orders_rn_copy;
                    CREATE TABLE wp_aieo_adv_stats_orders_rn_copy (
                        EponymousId INT,
                        OrderId BIGINT,
                        OrderDate DATETIME,
                        OrderItemsTotal DECIMAL(10,2),
                        rn INT,
                        INDEX idx_eponymous (EponymousId),
                        INDEX idx_eponymous_rn (EponymousId, rn)
                    ) ENGINE=InnoDB AS SELECT * FROM wp_aieo_adv_stats_orders_rn;
                END IF;

                -- Process customers in this batch using LIMIT/OFFSET
                -- First, create a temp table with the customer IDs for this batch (MariaDB workaround for LIMIT in subquery)
                DROP TEMPORARY TABLE IF EXISTS temp_batch_customers_adv;
                SET @sql_temp = CONCAT('
                    CREATE TEMPORARY TABLE temp_batch_customers_adv (
                        EponymousId INT PRIMARY KEY
                    ) ENGINE=MEMORY AS
                    SELECT DISTINCT EponymousId
                    FROM wp_aieo_adv_stats_orders_rn
                    WHERE EponymousId <> 0
                    ORDER BY EponymousId
                    LIMIT ', batch_size, ' OFFSET ', batch_offset, '
                ');
                PREPARE stmt_temp FROM @sql_temp;
                EXECUTE stmt_temp;
                DEALLOCATE PREPARE stmt_temp;

                -- Now update using the temp table
                SET @sql = CONCAT('
                UPDATE `', target_table_name, '` AS t1
                INNER JOIN (
                    SELECT a.EponymousId,
                           AVG(DATEDIFF(a.OrderDate, b.OrderDate)) AS AvgDBO,
                           MAX(CASE WHEN DATEDIFF(a.OrderDate, b.OrderDate) <> 0 THEN DATEDIFF(a.OrderDate, b.OrderDate) ELSE NULL END) AS MaxDBO,
                           MIN(CASE WHEN DATEDIFF(a.OrderDate, b.OrderDate) <> 0 THEN DATEDIFF(a.OrderDate, b.OrderDate) ELSE NULL END) AS MinDBO,
                           DATEDIFF(UTC_TIMESTAMP(), MAX(a.OrderDate)) AS CurrentDBO,
                           STDDEV(DATEDIFF(a.OrderDate, b.OrderDate)) AS SDDBO,
                           FIRST_VALUE(a.OrderItemsTotal) OVER (PARTITION BY a.EponymousId ORDER BY a.OrderDate DESC) AS LastOrderSpend
                    FROM wp_aieo_adv_stats_orders_rn AS a
                    LEFT JOIN wp_aieo_adv_stats_orders_rn_copy AS b ON a.EponymousId = b.EponymousId AND a.rn = b.rn + 1
                    WHERE a.EponymousId <> 0
                      AND a.EponymousId IN (SELECT EponymousId FROM temp_batch_customers_adv)
                    GROUP BY a.EponymousId
                ) AS t2 ON t1.EponymousId = t2.EponymousId
                SET
                    t1.AvgDBO = t2.AvgDBO,
                    t1.MaxDBO = t2.MaxDBO,
                    t1.MinDBO = t2.MinDBO,
                    t1.CurrentDBO = t2.CurrentDBO,
                    t1.SDDBO = t2.SDDBO,
                    t1.zDBO = (t2.CurrentDBO - t2.AvgDBO) / NULLIF(t2.SDDBO, 0),
                    t1.LastOrderSpend = t2.LastOrderSpend;
                ');

                PREPARE stmt2 FROM @sql;
                EXECUTE stmt2;
                DEALLOCATE PREPARE stmt2;

                -- Clean up batch temp table
                DROP TEMPORARY TABLE IF EXISTS temp_batch_customers_adv;

                -- Note: Persistent helper tables wp_aieo_adv_stats_orders_rn* will be cleaned up by PHP after completion
            END";

    aieo_drop_sp_AIEO_UpdateCustomerCentricAdvStats($sp_prefix);
    $wpdb->query($sql);
}

function aieo_drop_sp_AIEO_UpdateCustomerCentricAdvStats($sp_prefix)
{
    aieo_drop_sp_common($sp_prefix, "AIEO_UpdateCustomerCentricAdvStats");
}

function aieo_create_sp_AIEO_UpdateCustomerCentricStdStats($sp_prefix, $temp_orders_table_name)
{
    global $wpdb;

    $procedure_name = $sp_prefix . "AIEO_UpdateCustomerCentricStdStats";
    $base_order_stats_table = $wpdb->prefix . 'aieo_base_order_stats';
    $cumulative_stats_table = $wpdb->prefix . 'aieo_cumulative_customer_stats';
    $progress_table = $wpdb->prefix . 'aieo_customer_stats_progress';

    $sql = "CREATE PROCEDURE $procedure_name(
                IN target_table_name VARCHAR(255),
                IN TargetDate INT,
                IN batch_size INT
            )
            BEGIN
                DECLARE done INT DEFAULT FALSE;
                DECLARE customer_id INT;
                DECLARE total_customers INT;
                DECLARE processed_customers INT DEFAULT 0;

                -- Create progress tracking table
                DROP TABLE IF EXISTS $progress_table;
                CREATE TABLE $progress_table (
                    total INT,
                    processed INT,
                    last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
                ) ENGINE=MEMORY;

                -- STEP 1: Create base order stats table (same as v2 - fast bulk operation)
                DROP TABLE IF EXISTS $base_order_stats_table;

                CREATE TABLE $base_order_stats_table (
                    `EponymousId` INT NOT NULL,
                    `OrderId` BIGINT NOT NULL,
                    `OrderSpend` DECIMAL(12,2),
                    `OrderSize` INT,
                    `NumberOfOrders` INT,
                    INDEX idx_eponymous (EponymousId),
                    INDEX idx_order (OrderId)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

                SET @sql1 = CONCAT('
                    INSERT INTO $base_order_stats_table (EponymousId, OrderId, OrderSpend, OrderSize, NumberOfOrders)
                    SELECT
                        EponymousId,
                        OrderId,
                        MAX(OrderItemsTotal) as OrderSpend,
                        MAX(OrderSize) as OrderSize,
                        (SELECT COUNT(DISTINCT o2.OrderId)
                         FROM ', target_table_name, ' o2
                         WHERE o2.EponymousId = o1.EponymousId
                         AND o2.EponymousId <> 0) as NumberOfOrders
                    FROM ', target_table_name, ' o1
                    WHERE EponymousId <> 0
                    GROUP BY EponymousId, OrderId
                ');
                PREPARE stmt1 FROM @sql1;
                EXECUTE stmt1;
                DEALLOCATE PREPARE stmt1;

                -- STEP 2: Create cumulative stats table (same as v2 - fast self-join)
                DROP TABLE IF EXISTS $cumulative_stats_table;

                CREATE TABLE $cumulative_stats_table (
                    INDEX idx_eponymous_order (EponymousId, OrderId)
                )
                SELECT
                    ws1.OrderId,
                    ws1.EponymousId,
                    ws1.OrderSpend,
                    ws1.OrderSize,
                    ws1.NumberOfOrders,
                    ROUND(ws1.OrderSpend / NULLIF(ws1.OrderSize, 0), 2) as AveSpendPerItemPerOrder,
                    ROUND(AVG(ws2.OrderSpend), 2) as TotalAverageOrder,
                    ROUND(AVG(ws2.OrderSize), 2) as AveItemsPerOrder,
                    ROUND(STD(ws2.OrderSize), 2) as STDItemsOfUser,
                    ROUND(STD(ws2.OrderSize) / NULLIF(MIN(ws2.OrderSize), 0), 2) as STDPerMinItemsOfUser,
                    ROUND(STD(ws2.OrderSize) / NULLIF(AVG(ws2.OrderSize), 0), 2) as STDPerAverageItemsOfUser,
                    ROUND(STD(ws2.OrderSize) / NULLIF(MAX(ws2.OrderSize), 0), 2) as STDPerMaxItemsOfUser,
                    ROUND(AVG(ws2.OrderSpend), 2) as AveSpendPerOrder,
                    ROUND(STD(ws2.OrderSpend), 2) as STDSpendOfUser,
                    ROUND(STD(ws2.OrderSpend) / NULLIF(MIN(ws2.OrderSpend), 0), 2) as STDPerMinSpendOfUser,
                    ROUND(STD(ws2.OrderSpend) / NULLIF(AVG(ws2.OrderSpend), 0), 2) as STDPerAverageSpendOfUser,
                    ROUND(STD(ws2.OrderSpend) / NULLIF(MAX(ws2.OrderSpend), 0), 2) as STDPerMaxSpendOfUser
                FROM $base_order_stats_table ws1
                JOIN $base_order_stats_table ws2 ON ws1.EponymousId = ws2.EponymousId
                GROUP BY ws1.EponymousId, ws1.OrderId, ws1.OrderSize, ws1.NumberOfOrders, ws1.OrderSpend
                ORDER BY ws1.EponymousId;

                -- Get total distinct customers
                SELECT COUNT(DISTINCT EponymousId) INTO total_customers
                FROM $base_order_stats_table
                WHERE EponymousId <> 0;

                -- Initialize progress
                INSERT INTO $progress_table (total, processed) VALUES (total_customers, 0);

                -- STEP 3 & 4: Update customers in batches with progress tracking
                -- Cursor to iterate through distinct customers
                BEGIN
                    DECLARE customer_cursor CURSOR FOR
                        SELECT DISTINCT EponymousId
                        FROM $base_order_stats_table
                        WHERE EponymousId <> 0
                        ORDER BY EponymousId;

                    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

                    OPEN customer_cursor;

                    customer_loop: LOOP
                        FETCH customer_cursor INTO customer_id;
                        IF done THEN
                            LEAVE customer_loop;
                        END IF;

                        -- Update basic statistics for this customer
                        SET @sql2 = CONCAT('
                            UPDATE ', target_table_name, ' AS t1
                            INNER JOIN (
                                SELECT
                                    EponymousId,
                                    AVG(OrderSize) AS AvgOrderSize,
                                    MAX(OrderSize) AS MaxOrderSize,
                                    MIN(OrderSize) AS MinOrderSize,
                                    AVG(OrderSpend) AS AvgSpend,
                                    MAX(OrderSpend) AS MaxSpend,
                                    MIN(OrderSpend) AS MinSpend
                                FROM $base_order_stats_table
                                WHERE EponymousId = ', customer_id, '
                                GROUP BY EponymousId
                            ) AS cs ON t1.EponymousId = cs.EponymousId
                            SET
                                t1.AvgOrderSize = cs.AvgOrderSize,
                                t1.MaxOrderSize = cs.MaxOrderSize,
                                t1.MinOrderSize = cs.MinOrderSize,
                                t1.AvgSpend = cs.AvgSpend,
                                t1.MaxSpend = cs.MaxSpend,
                                t1.MinSpend = cs.MinSpend
                        ');
                        PREPARE stmt2 FROM @sql2;
                        EXECUTE stmt2;
                        DEALLOCATE PREPARE stmt2;

                        -- Update total customer statistics for this customer
                        SET @sql3 = CONCAT('
                            UPDATE ', target_table_name, ' AS t1
                            INNER JOIN (
                                SELECT
                                    EponymousId,
                                    SUM(PrimaryQty) AS TotalCustomerItems,
                                    COUNT(DISTINCT OrderId) AS TotalCustomerOrders,
                                    SUM(LineValue) AS TotalCustomerTurnover
                                FROM ', target_table_name, '
                                WHERE EponymousId = ', customer_id, '
                                GROUP BY EponymousId
                            ) AS cs ON t1.EponymousId = cs.EponymousId
                            SET
                                t1.TotalCustomerItems = cs.TotalCustomerItems,
                                t1.TotalCustomerOrders = cs.TotalCustomerOrders,
                                t1.TotalCustomerTurnover = cs.TotalCustomerTurnover
                        ');
                        PREPARE stmt3 FROM @sql3;
                        EXECUTE stmt3;
                        DEALLOCATE PREPARE stmt3;

                        SET processed_customers = processed_customers + 1;

                        -- Update progress every batch_size customers
                        IF MOD(processed_customers, batch_size) = 0 THEN
                            UPDATE $progress_table SET processed = processed_customers;
                        END IF;

                    END LOOP;

                    CLOSE customer_cursor;
                END;

                -- Final progress update
                UPDATE $progress_table SET processed = total_customers;

            END";

    aieo_drop_sp_AIEO_UpdateCustomerCentricStdStats($sp_prefix);
    $wpdb->query($sql);
}

function aieo_drop_core_product_attributes_table($sp_core_product_attributes_table_name)
{
  global $wpdb;

  
  $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$sp_core_product_attributes_table_name}'");

  if ($table_exists) {
    
    aieo_drop_index_if_exists('aieo_idx_cpb_brands_brand_id', $sp_core_product_attributes_table_name);
    aieo_drop_index_if_exists('aieo_idx_cpb_brands_product', $sp_core_product_attributes_table_name);
    aieo_drop_index_if_exists('aieo_idx_cpb_brands_parent_product', $sp_core_product_attributes_table_name);
    aieo_drop_index_if_exists('aieo_idx_cpb_effective', $sp_core_product_attributes_table_name);
  }

  
  $wpdb->query("DROP TABLE IF EXISTS `{$sp_core_product_attributes_table_name}`");
}

function aieo_drop_core_product_category_hierarchy_table($sp_core_product_category_hierarchy_table_name)
{
  global $wpdb;

  
  $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$sp_core_product_category_hierarchy_table_name}'");

  if ($table_exists) {
    
    aieo_drop_index_if_exists('aieo_idx_hierarchy_product_id', $sp_core_product_category_hierarchy_table_name);
    aieo_drop_index_if_exists('aieo_idx_wppch_t_category_id', $sp_core_product_category_hierarchy_table_name);
    aieo_drop_index_if_exists('aieo_idx_wppch_sl_category_id', $sp_core_product_category_hierarchy_table_name);
    aieo_drop_index_if_exists('aieo_idx_wppch_tl_category_id', $sp_core_product_category_hierarchy_table_name);
  }

  
  $wpdb->query("DROP TABLE IF EXISTS `{$sp_core_product_category_hierarchy_table_name}`");
}

function aieo_drop_core_product_vitals_table($sp_core_product_vitals_table_name)
{
  global $wpdb;

  
  $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$sp_core_product_vitals_table_name}'");

  if ($table_exists) {
    
    aieo_drop_index_if_exists('aieo_idx_vitals_product_id', $sp_core_product_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_vitals_wppv_parent_product', $sp_core_product_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_vitals_wppv_sku', $sp_core_product_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_vitals_wppv_parent_sku', $sp_core_product_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_vitals_wppv_stock', $sp_core_product_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_vitals_wppv_brand', $sp_core_product_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_vitals_wppv_menu_id', $sp_core_product_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_vitals_wppv_head_id', $sp_core_product_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_vitals_wppv_subcat_id', $sp_core_product_vitals_table_name);
  }

  
  $wpdb->query("DROP TABLE IF EXISTS `{$sp_core_product_vitals_table_name}`");
}

function aieo_drop_core_variation_vitals_table($sp_core_variation_vitals_table_name)
{
  global $wpdb;

  
  $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$sp_core_variation_vitals_table_name}'");

  if ($table_exists) {
    
    aieo_drop_index_if_exists('aieo_idx_var_vitals_product_id', $sp_core_variation_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_var_vitals_parent_id', $sp_core_variation_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_var_vitals_sku', $sp_core_variation_vitals_table_name);
    aieo_drop_index_if_exists('aieo_idx_var_vitals_erpid', $sp_core_variation_vitals_table_name);
  }

  
  $wpdb->query("DROP TABLE IF EXISTS `{$sp_core_variation_vitals_table_name}`");
}

function aieo_drop_core_reciprocal_products_table($sp_core_reciprocal_products_table_name)
{
  global $wpdb;

  
  $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$sp_core_reciprocal_products_table_name}'");

  if ($table_exists) {
    
    aieo_drop_index_if_exists('aieo_idx_rpt_source_product_id', $sp_core_reciprocal_products_table_name);
    aieo_drop_index_if_exists('aieo_idx_rpt_source_parent_product_id', $sp_core_reciprocal_products_table_name);
    aieo_drop_index_if_exists('aieo_idx_rpt_target_product_id', $sp_core_reciprocal_products_table_name);
    aieo_drop_index_if_exists('aieo_idx_rpt_target_parent_product_id', $sp_core_reciprocal_products_table_name);
    aieo_drop_index_if_exists('aieo_idx_rpt_target_sku', $sp_core_reciprocal_products_table_name);
  }

  
  $wpdb->query("DROP TABLE IF EXISTS `{$sp_core_reciprocal_products_table_name}`");
}

function aieo_drop_temp_orders_table($sp_temp_orders_table_name)
{
  global $wpdb;

  
  $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$sp_temp_orders_table_name}'");

  if ($table_exists) {
    
    aieo_drop_index_if_exists('aieo_idx_orderid_orders', $sp_temp_orders_table_name);
    aieo_drop_index_if_exists('aieo_idx_productid_orders', $sp_temp_orders_table_name);
    aieo_drop_index_if_exists('aieo_idx_parentproductid_orders', $sp_temp_orders_table_name);
    aieo_drop_index_if_exists('aieo_idx_brandid_orders', $sp_temp_orders_table_name);
    aieo_drop_index_if_exists('aieo_idx_eponymousid_orders', $sp_temp_orders_table_name);
  }

  
  $wpdb->query("DROP TABLE IF EXISTS `{$sp_temp_orders_table_name}`"); 
}

function aieo_drop_temp_store_orders_table($sp_temp_store_orders_table_name)
{
  global $wpdb;

  
  $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$sp_temp_store_orders_table_name}'");

  if ($table_exists) {
    
    aieo_drop_index_if_exists('aieo_idx_orderid_store_orders', $sp_temp_store_orders_table_name);
    aieo_drop_index_if_exists('aieo_idx_productid_store_orders', $sp_temp_store_orders_table_name);
    aieo_drop_index_if_exists('aieo_idx_parentproductid_store_orders', $sp_temp_store_orders_table_name);
    aieo_drop_index_if_exists('aieo_idx_brandid_store_orders', $sp_temp_store_orders_table_name);
    aieo_drop_index_if_exists('aieo_idx_eponymousid_store_orders', $sp_temp_store_orders_table_name);
    aieo_drop_index_if_exists('aieo_idx_loyaltyid_store_orders', $sp_temp_store_orders_table_name);
  }

  
  $wpdb->query("DROP TABLE IF EXISTS `{$sp_temp_store_orders_table_name}`"); 
}

function aieo_drop_temp_products_table($sp_temp_products_table_name)
{
  global $wpdb;

  
  $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$sp_temp_products_table_name}'");

  if ($table_exists) {
    
    aieo_drop_index_if_exists('aieo_idx_productid_products', $sp_temp_products_table_name);
    aieo_drop_index_if_exists('aieo_idx_parentproductid_products', $sp_temp_products_table_name);
    aieo_drop_index_if_exists('aieo_idx_brandid_products', $sp_temp_products_table_name);
  }

  
  $wpdb->query("DROP TABLE IF EXISTS `{$sp_temp_products_table_name}`"); 
}

function aieo_drop_sp_common($sp_prefix, $procedure_name_suffix)
{
  global $wpdb;

  
  $sp_prefix = sanitize_text_field($sp_prefix);
  if (!preg_match('/^\w+$/', $sp_prefix)) {
    
    wp_die(esc_html__('Invalid input detected in prefix', 'ai-eshop-optimizer'));
  }

  
  $procedure_name_suffix = sanitize_text_field($procedure_name_suffix);
  if (!preg_match('/^\w+$/', $procedure_name_suffix)) {
    
    wp_die(esc_html__('Invalid input detected in procedure name suffix', 'ai-eshop-optimizer'));
  }

  
  $procedure_name = $sp_prefix . $procedure_name_suffix;

  
  $query = "DROP PROCEDURE IF EXISTS `{$procedure_name}`";

  
  $wpdb->query($query);
}

function aieo_drop_sp_AIEO_UpdateOrdersWithUnsold($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_UpdateOrdersWithUnsold");
}

function aieo_drop_sp_AIEO_StripHTMLAndReplicateContentIntro($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_StripHTMLAndReplicateContentIntro");
}

function aieo_drop_sp_AIEO_RenameTable($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_RenameTable");
}

function aieo_drop_sp_AIEO_FindDifferences($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_FindDifferences");
}

function aieo_drop_sp_AIEO_Create_Product_Catalogue_HPOS($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_Create_Product_Catalogue_HPOS");
}

function aieo_drop_sp_AIEO_Export_Orders_HPOS($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_Export_Orders_HPOS");
}

function aieo_drop_sp_AIEO_Export_Store_Orders($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_Export_Store_Orders");
}

function aieo_drop_sp_AIEO_Export_Orders_non_HPOS($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_Export_Orders_non_HPOS");
}

function aieo_drop_sp_AIEO_Merge_Store_Orders($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_Merge_Store_Orders");

}

function aieo_drop_sp_AIEO_UpdateProductCentricStats($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_UpdateProductCentricStats");
}
function aieo_drop_sp_GenerateGraphDBUUIDs($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_GenerateGraphDBUUIDs");
}

function aieo_drop_sp_GenerateGraphDBUUIDFreqs($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_GenerateGraphDBUUIDFreqs");
}

function aieo_drop_sp_AIEO_GenerateGraphDBUUIDs($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_GenerateGraphDBUUIDs");
}

function aieo_drop_sp_AIEO_GenerateGraphDBUUIDFreqs($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_GenerateGraphDBUUIDFreqs");
}

function aieo_drop_sp_AIEO_UpdateContentWordCount($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_UpdateContentWordCount");
}

function aieo_drop_sp_AIEO_UpdateContentOutgoingLinks($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_UpdateContentOutgoingLinks");
}

function aieo_drop_sp_AIEO_UpdateContentIntroPlain($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_UpdateContentIntroPlain");
}

function aieo_drop_sp_AIEO_Include_Products_Without_Sales()
{
  global $wpdb;
  $wpdb->query("DROP PROCEDURE IF EXISTS AIEO_Include_Products_Without_Sales");
}

function aieo_drop_sp_AIEO_PopulateProductNeeds($sp_prefix)
{
  aieo_drop_sp_common($sp_prefix, "AIEO_PopulateProductNeeds");
}

function aieo_create_sp_AIEO_PopulateProductNeeds($sp_prefix)
{
  global $wpdb;

  
  $needs_attribute = get_option('aieo_core_attribute_need', 'pa_need');

  
  $sp_prefix = sanitize_text_field($sp_prefix);
  if (!preg_match('/^\w+$/', $sp_prefix)) {
    wp_die(esc_html__('Invalid input detected in prefix', 'ai-eshop-optimizer'));
  }

  $procedure_name = $sp_prefix . 'AIEO_PopulateProductNeeds';

  $sql = "
CREATE PROCEDURE `{$procedure_name}`()
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;

    START TRANSACTION;

    -- Step 1: Build parent needs with correct ordering
    DROP TEMPORARY TABLE IF EXISTS tmp_parent_needs;
    CREATE TEMPORARY TABLE tmp_parent_needs (
        parent_id      INT NOT NULL PRIMARY KEY,
        ids_csv        VARCHAR(2000) NULL,
        primary_id     INT NULL,
        secondary_id   INT NULL,
        rest_ids       VARCHAR(1800) NULL,
        primary_name   VARCHAR(255) NULL,
        secondary_name VARCHAR(255) NULL,
        other_names    TEXT NULL
    ) ENGINE=InnoDB;

    -- Get parent products with their needs ordered by term name
    INSERT INTO tmp_parent_needs (parent_id, ids_csv, primary_id, secondary_id, rest_ids)
    SELECT
        pv.product_id AS parent_id,

        -- Get all need IDs ordered by term name
        GROUP_CONCAT(
            tt.term_id
            ORDER BY t.name ASC
            SEPARATOR ','
        ) AS ids_csv,

        -- Extract first ID as primary
        CAST(SUBSTRING_INDEX(
            GROUP_CONCAT(
                tt.term_id
                ORDER BY t.name ASC
                SEPARATOR ','
            ),
            ',', 1
        ) AS UNSIGNED) AS primary_id,

        -- Extract second ID as secondary
        CASE
            WHEN (LENGTH(GROUP_CONCAT(tt.term_id ORDER BY t.name ASC SEPARATOR ','))
                  - LENGTH(REPLACE(GROUP_CONCAT(tt.term_id ORDER BY t.name ASC SEPARATOR ','), ',', ''))) >= 1
            THEN CAST(SUBSTRING_INDEX(
                    SUBSTRING_INDEX(
                        GROUP_CONCAT(tt.term_id ORDER BY t.name ASC SEPARATOR ','),
                        ',', 2
                    ),
                    ',', -1
                ) AS UNSIGNED)
            ELSE NULL
        END AS secondary_id,

        -- Extract remaining IDs (3rd onwards)
        CASE
            WHEN (LENGTH(GROUP_CONCAT(tt.term_id ORDER BY t.name ASC SEPARATOR ','))
                  - LENGTH(REPLACE(GROUP_CONCAT(tt.term_id ORDER BY t.name ASC SEPARATOR ','), ',', ''))) >= 2
            THEN SUBSTRING(
                    GROUP_CONCAT(tt.term_id ORDER BY t.name ASC SEPARATOR ','),
                    LOCATE(',', GROUP_CONCAT(tt.term_id ORDER BY t.name ASC SEPARATOR ','),
                           LOCATE(',', GROUP_CONCAT(tt.term_id ORDER BY t.name ASC SEPARATOR ',')) + 1) + 1
                )
            ELSE ''
        END AS rest_ids

    FROM {$wpdb->prefix}aieo_core_product_vitals pv
    JOIN {$wpdb->prefix}posts p
        ON p.ID = pv.product_id
        AND pv.product_id = pv.parent_id  -- parents only
        AND pv.status = 1
    JOIN {$wpdb->prefix}term_relationships tr
        ON tr.object_id = p.ID
    JOIN {$wpdb->prefix}term_taxonomy tt
        ON tt.term_taxonomy_id = tr.term_taxonomy_id
        AND tt.taxonomy = '{$needs_attribute}'
    JOIN {$wpdb->prefix}terms t
        ON t.term_id = tt.term_id
    GROUP BY pv.product_id
    HAVING ids_csv IS NOT NULL;

    -- Step 2: Resolve primary/secondary names
    UPDATE tmp_parent_needs n
    LEFT JOIN {$wpdb->prefix}terms t1 ON t1.term_id = n.primary_id
    LEFT JOIN {$wpdb->prefix}terms t2 ON t2.term_id = n.secondary_id
    SET n.primary_name   = COALESCE(t1.name, ''),
        n.secondary_name = COALESCE(t2.name, '');

    -- Step 3: Build other_names from rest_ids list
    DROP TEMPORARY TABLE IF EXISTS tmp_parent_other;
    CREATE TEMPORARY TABLE tmp_parent_other (
        parent_id   INT NOT NULL PRIMARY KEY,
        other_names TEXT NULL
    ) ENGINE=InnoDB;

    INSERT INTO tmp_parent_other (parent_id, other_names)
    SELECT
        n.parent_id,
        GROUP_CONCAT(t.name ORDER BY FIND_IN_SET(t.term_id, n.rest_ids) SEPARATOR ', ')
    FROM tmp_parent_needs n
    JOIN {$wpdb->prefix}term_taxonomy tt
        ON tt.taxonomy = '{$needs_attribute}'
        AND n.rest_ids <> ''
        AND FIND_IN_SET(tt.term_id, n.rest_ids) > 0
    JOIN {$wpdb->prefix}terms t
        ON t.term_id = tt.term_id
    GROUP BY n.parent_id;

    UPDATE tmp_parent_needs n
    LEFT JOIN tmp_parent_other o ON o.parent_id = n.parent_id
    SET n.other_names = COALESCE(o.other_names, '');

    -- Step 4: Apply to PARENTS in vitals table
    UPDATE {$wpdb->prefix}aieo_core_product_vitals pv
    JOIN tmp_parent_needs n ON n.parent_id = pv.product_id
    SET
        pv.primary_need_id   = NULLIF(n.primary_id, 0),
        pv.secondary_need_id = NULLIF(n.secondary_id, 0),
        pv.other_needs_ids   = COALESCE(n.rest_ids, ''),
        pv.primary_need      = COALESCE(n.primary_name, ''),
        pv.secondary_need    = COALESCE(n.secondary_name, ''),
        pv.other_needs       = COALESCE(n.other_names, '')
    WHERE pv.parent_id = pv.product_id
        AND pv.status = 1;

    -- Step 5: Propagate to VARIATIONS (children) from their parents
    UPDATE {$wpdb->prefix}aieo_core_product_vitals pvChild
    JOIN tmp_parent_needs n ON n.parent_id = pvChild.parent_id
    SET
        pvChild.primary_need_id   = NULLIF(n.primary_id, 0),
        pvChild.secondary_need_id = NULLIF(n.secondary_id, 0),
        pvChild.other_needs_ids   = COALESCE(n.rest_ids, ''),
        pvChild.primary_need      = COALESCE(n.primary_name, ''),
        pvChild.secondary_need    = COALESCE(n.secondary_name, ''),
        pvChild.other_needs       = COALESCE(n.other_names, '')
    WHERE pvChild.product_id <> pvChild.parent_id
        AND pvChild.status = 1;

    -- Cleanup
    DROP TEMPORARY TABLE IF EXISTS tmp_parent_other;
    DROP TEMPORARY TABLE IF EXISTS tmp_parent_needs;

    COMMIT;
END";

  $wpdb->query($sql);
}

function aieo_drop_function_AIEO_StripHTML()
{
  global $wpdb;
  $wpdb->query("DROP FUNCTION IF EXISTS AIEO_StripHTML");
}

function aieo_drop_function_AIEO_CountWords()
{
  global $wpdb;
  $wpdb->query("DROP FUNCTION IF EXISTS AIEO_CountWords");
}

function aieo_drop_function_AIEO_CountLinks()
{
  global $wpdb;
  $wpdb->query("DROP FUNCTION IF EXISTS AIEO_CountLinks");
}

function aieo_create_sessions_table()
{
    global $wpdb;
    $table_name = $wpdb->prefix . 'aieo_sessions';

    
    aieo_create_session_statics_table();

    
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") === $table_name;

    if ($table_exists) {
        
        $has_old_structure = $wpdb->get_var(
            "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
             WHERE TABLE_SCHEMA = DATABASE()
             AND TABLE_NAME = '{$table_name}'
             AND COLUMN_NAME = 'user_agent'"
        );

        if ($has_old_structure) {
            
            aieo_migrate_sessions_to_statics();

            
            $wpdb->query("ALTER TABLE {$table_name} DROP COLUMN IF EXISTS user_id");
            $wpdb->query("ALTER TABLE {$table_name} DROP COLUMN IF EXISTS user_agent");
            $wpdb->query("ALTER TABLE {$table_name} DROP COLUMN IF EXISTS device_type");
            $wpdb->query("ALTER TABLE {$table_name} DROP COLUMN IF EXISTS browser");
            $wpdb->query("ALTER TABLE {$table_name} DROP COLUMN IF EXISTS os");
            $wpdb->query("ALTER TABLE {$table_name} DROP COLUMN IF EXISTS referrer");
        }

        
        
        
        
        if ( ! $wpdb->get_var( "SHOW COLUMNS FROM {$table_name} LIKE 'event_id'" ) ) {
            $wpdb->query( "ALTER TABLE {$table_name} ADD COLUMN event_id VARCHAR(36) DEFAULT NULL AFTER id" );
            $wpdb->query( "ALTER TABLE {$table_name} ADD UNIQUE KEY uq_event_id (event_id)" );
        }
    } else {
        
        $charset_collate = aieo_get_charset_collate();

        $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            event_id VARCHAR(36) DEFAULT NULL COMMENT 'Client-minted id for idempotent INSERT IGNORE (Fix-A §8.2)',
            session_id VARCHAR(255) NOT NULL,
            user_id BIGINT UNSIGNED DEFAULT NULL,
            event_type VARCHAR(50) NOT NULL,
            product_id BIGINT(20) UNSIGNED DEFAULT NULL COMMENT 'Extracted from event_data',
            product_name VARCHAR(512) DEFAULT NULL,
            category_id BIGINT(20) UNSIGNED DEFAULT NULL,
            category_name VARCHAR(512) DEFAULT NULL COMMENT 'Decoded category name',
            scroll_depth TINYINT(3) UNSIGNED DEFAULT NULL COMMENT '0-100%',
            click_count SMALLINT(5) UNSIGNED DEFAULT NULL,

            -- SEQUENCE & POSITION ANALYTICS (for order analysis)
            event_sequence INT(10) UNSIGNED DEFAULT NULL COMMENT 'Position in session (1st, 2nd, 3rd event)',
            is_first_event TINYINT(1) DEFAULT 0 COMMENT '1 if landing page/product',
            is_last_event TINYINT(1) DEFAULT 0 COMMENT '1 if exit page/product',
            is_only_event TINYINT(1) DEFAULT 0 COMMENT '1 if bounce (first AND last)',

            -- TIME & ATTENTION ANALYTICS (for JLW/VLW calculations)
            time_on_page INT(10) UNSIGNED DEFAULT NULL COMMENT 'Seconds spent on this page/product',
            visit_number SMALLINT(5) UNSIGNED DEFAULT NULL COMMENT 'Nth visit to this product in session (hesitation)',

            -- CONVERSION & CHECKOUT TRACKING
            order_id BIGINT(20) UNSIGNED DEFAULT NULL COMMENT 'WooCommerce order ID if session led to purchase',
            checkout_step VARCHAR(50) DEFAULT NULL COMMENT 'cart, billing, shipping, payment, order_received',

            event_data LONGTEXT,
            page_url VARCHAR(512),
            timestamp BIGINT UNSIGNED NOT NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uq_event_id (event_id),
            -- DB #33 index trim: idx_session_id(session_id) and
            -- idx_product_id(product_id) are intentionally NOT declared here.
            -- Both are pure left-prefixes of wider composites that already
            -- serve those single-column lookups (idx_session_product /
            -- idx_sequence cover session_id; idx_product_event covers
            -- product_id), so they were write amplification with no read
            -- benefit on this ~5s-per-visitor INSERT table. Existing installs
            -- get them dropped by aieo_run_db_migrations() (db_version 1).
            INDEX idx_user_id (user_id),
            INDEX idx_event_type (event_type),
            INDEX idx_timestamp (timestamp),
            INDEX idx_created_at (created_at),
            INDEX idx_product_event (product_id, event_type, timestamp),
            INDEX idx_category_id (category_id),
            INDEX idx_session_product (session_id, product_id, event_type),
            INDEX idx_sequence (session_id, event_sequence),
            INDEX idx_first_page (product_id, is_first_event),
            INDEX idx_last_page (product_id, is_last_event),
            INDEX idx_bounce (product_id, is_only_event),
            INDEX idx_order_id (order_id),
            INDEX idx_checkout_step (checkout_step),
            INDEX idx_hesitation (product_id, visit_number)
        ) {$charset_collate};";

        $wpdb->query($sql);
    }
}

function aieo_migrate_sessions_to_statics()
{
    global $wpdb;
    $sessions_table = $wpdb->prefix . 'aieo_sessions';
    $statics_table = $wpdb->prefix . 'aieo_sessions_statics';

    
    
    $wpdb->query("
        INSERT IGNORE INTO {$statics_table}
            (session_id, user_agent, device_type, browser, os, landing_page, referrer, first_timestamp, created_at)
        SELECT
            session_id,
            user_agent,
            device_type,
            browser,
            os,
            page_url AS landing_page,
            referrer,
            MIN(timestamp) AS first_timestamp,
            MIN(created_at) AS created_at
        FROM {$sessions_table}
        GROUP BY session_id
    ");
}

function aieo_create_session_statics_table()
{
    global $wpdb;
    $table_name = $wpdb->prefix . 'aieo_sessions_statics';
    $charset_collate = aieo_get_charset_collate();

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        session_id VARCHAR(255) NOT NULL UNIQUE,
        user_id BIGINT UNSIGNED DEFAULT NULL,
        user_agent TEXT,
        device_type VARCHAR(50),
        browser VARCHAR(100),
        os VARCHAR(100),
        landing_page VARCHAR(512),
        referrer VARCHAR(512),
        entry_channel VARCHAR(40) NOT NULL DEFAULT '',
        entry_source VARCHAR(80) NOT NULL DEFAULT '',
        entry_campaign VARCHAR(120) NOT NULL DEFAULT '',
        entry_medium VARCHAR(40) NOT NULL DEFAULT '',
        entry_term VARCHAR(80) NOT NULL DEFAULT '',
        entry_content VARCHAR(80) NOT NULL DEFAULT '',
        entry_campaign_id BIGINT UNSIGNED DEFAULT NULL,
        first_timestamp BIGINT UNSIGNED NOT NULL,
        last_timestamp BIGINT UNSIGNED DEFAULT NULL,
        total_events INT(10) UNSIGNED DEFAULT 0,
        total_product_views INT(10) UNSIGNED DEFAULT 0,
        total_add_to_cart INT(10) UNSIGNED DEFAULT 0,
        total_scroll_events INT(10) UNSIGNED DEFAULT 0,
        total_click_events INT(10) UNSIGNED DEFAULT 0,
        session_duration_seconds INT(10) UNSIGNED DEFAULT 0,
        order_id BIGINT(20) UNSIGNED DEFAULT NULL COMMENT 'WC order id if session led to a purchase',
        order_total DECIMAL(12,2) DEFAULT NULL COMMENT 'WC order grand total snapshot',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_device_type (device_type),
        INDEX idx_first_timestamp (first_timestamp),
        INDEX idx_created_at (created_at),
        INDEX idx_updated_at (updated_at),
        INDEX idx_entry_channel (entry_channel),
        INDEX aieo_idx_ss_camp_id (entry_campaign_id),
        INDEX aieo_idx_ss_order_id (order_id)
    ) {$charset_collate};";

    $wpdb->query($sql);
}

function aieo_drop_sessions_table()
{
    global $wpdb;

    
    $sessions_table = $wpdb->prefix . 'aieo_sessions';
    $wpdb->query("DROP TABLE IF EXISTS {$sessions_table}");

    
    $statics_table = $wpdb->prefix . 'aieo_sessions_statics';
    $wpdb->query("DROP TABLE IF EXISTS {$statics_table}");

    
    $archive_table = $wpdb->prefix . 'aieo_sessions_archive';
    $wpdb->query("DROP TABLE IF EXISTS {$archive_table}");

    $archive_statics_table = $wpdb->prefix . 'aieo_sessions_statics_archive';
    $wpdb->query("DROP TABLE IF EXISTS {$archive_statics_table}");
}

function aieo_create_session_archive_tables()
{
    global $wpdb;
    $charset_collate = aieo_get_charset_collate();

    
    $archive_table = $wpdb->prefix . 'aieo_sessions_archive';
    $sql_archive = "CREATE TABLE IF NOT EXISTS {$archive_table} (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        session_id VARCHAR(255) NOT NULL,
        user_id BIGINT UNSIGNED DEFAULT NULL,
        event_type VARCHAR(50) NOT NULL,
        product_id BIGINT(20) UNSIGNED DEFAULT NULL COMMENT 'Extracted from event_data',
        product_name VARCHAR(512) DEFAULT NULL,
        category_id BIGINT(20) UNSIGNED DEFAULT NULL,
        category_name VARCHAR(512) DEFAULT NULL COMMENT 'Decoded category name',
        scroll_depth TINYINT(3) UNSIGNED DEFAULT NULL COMMENT '0-100%',
        click_count SMALLINT(5) UNSIGNED DEFAULT NULL,

        -- SEQUENCE & POSITION ANALYTICS (for order analysis)
        event_sequence INT(10) UNSIGNED DEFAULT NULL COMMENT 'Position in session (1st, 2nd, 3rd event)',
        is_first_event TINYINT(1) DEFAULT 0 COMMENT '1 if landing page/product',
        is_last_event TINYINT(1) DEFAULT 0 COMMENT '1 if exit page/product',
        is_only_event TINYINT(1) DEFAULT 0 COMMENT '1 if bounce (first AND last)',

        -- TIME & ATTENTION ANALYTICS (for JLW/VLW calculations)
        time_on_page INT(10) UNSIGNED DEFAULT NULL COMMENT 'Seconds spent on this page/product',
        visit_number SMALLINT(5) UNSIGNED DEFAULT NULL COMMENT 'Nth visit to this product in session (hesitation)',

        -- CONVERSION & CHECKOUT TRACKING
        order_id BIGINT(20) UNSIGNED DEFAULT NULL COMMENT 'WooCommerce order ID if session led to purchase',
        checkout_step VARCHAR(50) DEFAULT NULL COMMENT 'cart, billing, shipping, payment, order_received',

        event_data LONGTEXT,
        page_url VARCHAR(512),
        timestamp BIGINT UNSIGNED NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        archived_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        -- DB #33 index trim: idx_session_id / idx_product_id omitted (left-prefix
        -- duplicates of idx_sequence / idx_product_event). Existing installs get
        -- them dropped by aieo_run_db_migrations() (db_version 1).
        INDEX idx_user_id (user_id),
        INDEX idx_event_type (event_type),
        INDEX idx_timestamp (timestamp),
        INDEX idx_created_at (created_at),
        INDEX idx_archived_at (archived_at),
        INDEX idx_product_event (product_id, event_type, timestamp),
        INDEX idx_category_id (category_id),
        INDEX idx_sequence (session_id, event_sequence),
        INDEX idx_first_page (product_id, is_first_event),
        INDEX idx_last_page (product_id, is_last_event),
        INDEX idx_bounce (product_id, is_only_event),
        INDEX idx_order_id (order_id),
        INDEX idx_checkout_step (checkout_step),
        INDEX idx_hesitation (product_id, visit_number)
    ) {$charset_collate};";

    $wpdb->query($sql_archive);

    
    $archive_statics_table = $wpdb->prefix . 'aieo_sessions_statics_archive';
    $sql_archive_statics = "CREATE TABLE IF NOT EXISTS {$archive_statics_table} (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        session_id VARCHAR(255) NOT NULL,
        user_id BIGINT UNSIGNED DEFAULT NULL,
        user_agent TEXT,
        device_type VARCHAR(50),
        browser VARCHAR(100),
        os VARCHAR(100),
        landing_page VARCHAR(512),
        referrer VARCHAR(512),
        first_timestamp BIGINT UNSIGNED NOT NULL,
        last_timestamp BIGINT UNSIGNED DEFAULT NULL,
        total_events INT(10) UNSIGNED DEFAULT 0,
        total_product_views INT(10) UNSIGNED DEFAULT 0,
        total_add_to_cart INT(10) UNSIGNED DEFAULT 0,
        total_scroll_events INT(10) UNSIGNED DEFAULT 0,
        total_click_events INT(10) UNSIGNED DEFAULT 0,
        session_duration_seconds INT(10) UNSIGNED DEFAULT 0,
        created_at DATETIME,
        updated_at DATETIME,
        archived_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_device_type (device_type),
        INDEX idx_first_timestamp (first_timestamp),
        INDEX idx_created_at (created_at),
        INDEX idx_archived_at (archived_at)
    ) {$charset_collate};";

    $wpdb->query($sql_archive_statics);
}

function aieo_create_session_journeys_table()
{
    global $wpdb;
    $table_name = $wpdb->prefix . 'aieo_session_journeys';
    $charset_collate = aieo_get_charset_collate();

    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        session_id VARCHAR(255) NOT NULL,
        user_id BIGINT(20) UNSIGNED DEFAULT NULL,
        product_id BIGINT(20) UNSIGNED NOT NULL,
        product_name VARCHAR(512) DEFAULT NULL,
        event_type ENUM('product_view','add_to_cart','remove_from_cart','add_to_wishlist') NOT NULL,
        event_timestamp BIGINT(20) UNSIGNED NOT NULL,
        event_sequence INT(10) UNSIGNED NOT NULL,
        total_events_in_session INT(10) UNSIGNED NOT NULL,
        is_first_event TINYINT(1) DEFAULT 0,
        is_last_event TINYINT(1) DEFAULT 0,
        journey_position DECIMAL(5,4) DEFAULT NULL,
        time_on_page INT(10) UNSIGNED DEFAULT 0,
        max_scroll_depth TINYINT(3) UNSIGNED DEFAULT 0,
        click_count SMALLINT(5) UNSIGNED DEFAULT 0,
        device_type VARCHAR(50) DEFAULT NULL,
        session_duration INT(10) UNSIGNED DEFAULT 0,
        session_event_count INT(10) UNSIGNED DEFAULT 0,
        did_add_to_cart TINYINT(1) DEFAULT 0,
        add_to_cart_sequence INT(10) DEFAULT NULL,
        time_to_cart INT(10) DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY idx_session_id (session_id),
        KEY idx_product_id (product_id),
        KEY idx_event_type (event_type),
        KEY idx_sequence (session_id, event_sequence),
        KEY idx_first_page (product_id, is_first_event),
        KEY idx_last_page (product_id, is_last_event),
        KEY idx_conversion (product_id, did_add_to_cart),
        KEY idx_timestamp (event_timestamp),
        KEY idx_created_at (created_at),
        KEY idx_product_event (product_id, event_type, event_timestamp)
    ) {$charset_collate};";

    $wpdb->query($sql);
}

function aieo_create_product_session_metrics_table()
{
    global $wpdb;
    $table_name = $wpdb->prefix . 'aieo_product_session_metrics';
    $charset_collate = aieo_get_charset_collate();

    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        product_id BIGINT(20) UNSIGNED NOT NULL,
        total_views INT(10) UNSIGNED DEFAULT 0,
        unique_sessions_viewed INT(10) UNSIGNED DEFAULT 0,
        avg_time_on_page DECIMAL(10,2) DEFAULT 0,
        avg_scroll_depth DECIMAL(5,2) DEFAULT 0,
        avg_clicks_per_view DECIMAL(5,2) DEFAULT 0,
        times_first_page INT(10) UNSIGNED DEFAULT 0,
        times_last_page INT(10) UNSIGNED DEFAULT 0,
        times_only_page INT(10) UNSIGNED DEFAULT 0,
        total_add_to_cart INT(10) UNSIGNED DEFAULT 0,
        unique_sessions_added_to_cart INT(10) UNSIGNED DEFAULT 0,
        view_to_cart_rate DECIMAL(5,4) DEFAULT 0,
        avg_time_to_cart DECIMAL(10,2) DEFAULT 0,
        view_to_cart_rank INT(10) UNSIGNED DEFAULT 0,
        engagement_score DECIMAL(10,2) DEFAULT 0,
        engagement_rank INT(10) UNSIGNED DEFAULT 0,
        bounce_rate DECIMAL(5,4) DEFAULT 0,
        bounce_rank INT(10) UNSIGNED DEFAULT 0,
        last_calculated DATETIME DEFAULT CURRENT_TIMESTAMP,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY idx_product_id (product_id),
        KEY idx_view_to_cart_rate (view_to_cart_rate),
        KEY idx_engagement_score (engagement_score),
        KEY idx_bounce_rate (bounce_rate),
        KEY idx_view_to_cart_rank (view_to_cart_rank),
        KEY idx_engagement_rank (engagement_rank),
        KEY idx_bounce_rank (bounce_rank),
        KEY idx_last_calculated (last_calculated)
    ) {$charset_collate};";

    $wpdb->query($sql);
}

function aieo_create_chat_embeddings_table()
{
    global $wpdb;
    $table_name = $wpdb->prefix . 'aieo_chat_embeddings';
    $charset_collate = aieo_get_charset_collate();

    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        post_id BIGINT(20) UNSIGNED NOT NULL,
        post_type VARCHAR(50) NOT NULL DEFAULT 'product',
        engine_type VARCHAR(50) NOT NULL DEFAULT 'voyage-2',
        deduplicated TINYINT(1) NOT NULL DEFAULT 0,
        embedding LONGTEXT NOT NULL,
        content_hash VARCHAR(32) NOT NULL,
        failure_count INT(11) NULL DEFAULT 0 COMMENT 'Number of failed embedding generation attempts',
        last_failure_reason VARCHAR(500) NULL COMMENT 'Reason for last failure',
        last_failure_date DATETIME NULL COMMENT 'Timestamp of last failure',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY post_id_type_engine_dedup (post_id, post_type, engine_type, deduplicated),
        KEY post_type (post_type),
        KEY engine_type (engine_type),
        KEY deduplicated (deduplicated),
        KEY created_at (created_at),
        KEY lookup_idx (post_id, engine_type, deduplicated),
        KEY stats_idx (engine_type, deduplicated, created_at),
        KEY failure_idx (failure_count, last_failure_date)
    ) {$charset_collate};";

    $wpdb->query($sql);
}

function aieo_drop_chat_embeddings_table()
{
    global $wpdb;
    $table_name = $wpdb->prefix . 'aieo_chat_embeddings';

    
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'");

    if ($table_exists) {
        
        aieo_drop_index_if_exists('aieo_idx_chat_emb_post_id_type', $table_name);
        aieo_drop_index_if_exists('aieo_idx_chat_emb_post_type', $table_name);
        aieo_drop_index_if_exists('aieo_idx_chat_emb_engine_type', $table_name);
        aieo_drop_index_if_exists('aieo_idx_chat_emb_created_at', $table_name);
    }

    
    $wpdb->query("DROP TABLE IF EXISTS {$table_name}");
}

function aieo_create_chat_history_table()
{
    global $wpdb;
    $table_name = $wpdb->prefix . 'aieo_chat_history';
    $charset_collate = aieo_get_charset_collate();

    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        conversation_id VARCHAR(64) NOT NULL,
        session_id VARCHAR(64) NOT NULL,
        user_id BIGINT(20) UNSIGNED DEFAULT NULL,
        user_message TEXT NOT NULL,
        assistant_message TEXT NOT NULL,
        model_used VARCHAR(100) DEFAULT 'claude-3-5-sonnet-20241022',
        embedder_used VARCHAR(50) DEFAULT NULL,
        products_found TEXT DEFAULT NULL,
        potential_suggestions_found TEXT DEFAULT NULL,
        user_rating TINYINT UNSIGNED DEFAULT NULL COMMENT 'User feedback rating 0-100, only stored if changed from default 50',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY conversation_id (conversation_id),
        KEY session_id (session_id),
        KEY user_id (user_id),
        KEY created_at (created_at),
        KEY user_rating (user_rating)
    ) {$charset_collate};";

    $wpdb->query($sql);
}

function aieo_drop_chat_history_table()
{
    global $wpdb;
    $table_name = $wpdb->prefix . 'aieo_chat_history';

    
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'");

    if ($table_exists) {
        
        aieo_drop_index_if_exists('aieo_idx_chat_hist_conversation_id', $table_name);
        aieo_drop_index_if_exists('aieo_idx_chat_hist_session_id', $table_name);
        aieo_drop_index_if_exists('aieo_idx_chat_hist_user_id', $table_name);
        aieo_drop_index_if_exists('aieo_idx_chat_hist_created_at', $table_name);
    }

    
    $wpdb->query("DROP TABLE IF EXISTS {$table_name}");
}

function aieo_create_ai_models_table()
{
    global $wpdb;
    $table_name = $wpdb->prefix . 'aieo_ai_models';
    $charset_collate = aieo_get_charset_collate();

    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        provider varchar(50) NOT NULL,
        model_id varchar(100) NOT NULL,
        model_name varchar(255) NOT NULL,
        model_type varchar(50) NOT NULL DEFAULT 'chat',
        description text DEFAULT NULL,
        max_tokens int(11) DEFAULT NULL,
        dimensions int(11) DEFAULT NULL,
        is_recommended tinyint(1) DEFAULT 0,
        is_active tinyint(1) DEFAULT 1,
        sort_order int(11) DEFAULT 0,
        metadata longtext DEFAULT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY provider_model (provider, model_id),
        KEY model_type (model_type),
        KEY is_active (is_active),
        KEY sort_order (sort_order)
    ) {$charset_collate};";

    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);

    
    $count = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name}");
    if ($count == 0) {
        aieo_insert_default_ai_models($table_name);
    }
}

function aieo_insert_default_ai_models($table_name)
{
    global $wpdb;

    $models = array(
        
        array('anthropic', 'claude-opus-4-5-20251101', 'Claude Opus 4.5', 'chat', 'Most intelligent model - hybrid reasoning', 16384, null, 1, 1, 10),
        array('anthropic', 'claude-sonnet-4-5-20250929', 'Claude Sonnet 4.5', 'chat', 'Balanced performance and speed', 16384, null, 0, 1, 20),
        array('anthropic', 'claude-haiku-4-5-20251001', 'Claude Haiku 4.5', 'chat', 'Fastest and most economical', 8192, null, 0, 1, 30),
        array('anthropic', 'claude-opus-4-1-20250805', 'Claude Opus 4.1', 'chat', 'Advanced reasoning capabilities', 8192, null, 0, 1, 40),
        array('anthropic', 'claude-sonnet-4-20250514', 'Claude Sonnet 4', 'chat', 'Balanced Claude 4 model', 8192, null, 0, 1, 50),
        array('anthropic', 'claude-3-5-sonnet-20241022', 'Claude 3.5 Sonnet', 'chat', 'Previous generation model', 8192, null, 0, 1, 60),
        array('anthropic', 'claude-3-5-haiku-20241022', 'Claude 3.5 Haiku', 'chat', 'Fast and economical', 8192, null, 0, 1, 70),

        
        array('openai', 'gpt-4o', 'GPT-4o', 'chat', 'Most capable multimodal model', 16384, null, 1, 1, 10),
        array('openai', 'gpt-4o-mini', 'GPT-4o Mini', 'chat', 'Fast and economical', 16384, null, 0, 1, 20),
        array('openai', 'gpt-4-turbo', 'GPT-4 Turbo', 'chat', 'GPT-4 Turbo with vision', 4096, null, 0, 1, 30),
        array('openai', 'o1', 'O1', 'chat', 'Advanced reasoning model', 32768, null, 0, 1, 40),
        array('openai', 'o1-mini', 'O1 Mini', 'chat', 'Reasoning model - economical', 16384, null, 0, 1, 50),

        
        array('openai', 'text-embedding-3-large', 'Text Embedding 3 Large', 'embedding', 'Best quality embeddings', null, 3072, 1, 1, 10),
        array('openai', 'text-embedding-3-small', 'Text Embedding 3 Small', 'embedding', 'Good quality, lower cost', null, 1536, 0, 1, 20),
        array('openai', 'text-embedding-ada-002', 'Text Embedding Ada 002', 'embedding', 'Legacy embedding model', null, 1536, 0, 1, 30),

        
        array('voyage', 'voyage-3', 'Voyage 3', 'embedding', 'Latest general-purpose model', null, 1024, 1, 1, 10),
        array('voyage', 'voyage-3-lite', 'Voyage 3 Lite', 'embedding', 'Faster, lower cost', null, 512, 0, 1, 20),
        array('voyage', 'voyage-3-large', 'Voyage 3 Large', 'embedding', 'Best quality, larger dimension', null, 2048, 0, 1, 15),
        array('voyage', 'voyage-3.5', 'Voyage 3.5', 'embedding', 'Newest generation', null, 1024, 0, 1, 5),
        array('voyage', 'voyage-3.5-lite', 'Voyage 3.5 Lite', 'embedding', 'Fast newest generation', null, 1024, 0, 1, 8),
        array('voyage', 'voyage-multilingual-2', 'Voyage Multilingual 2', 'embedding', 'Optimized for non-English', null, 1024, 0, 1, 25),
        array('voyage', 'voyage-large-2', 'Voyage Large 2', 'embedding', 'High quality embeddings', null, 1536, 0, 1, 30),
        array('voyage', 'voyage-2', 'Voyage 2', 'embedding', 'Legacy model', null, 1024, 0, 1, 40),
    );

    
    
    
    
    
    
    foreach ($models as $model) {
        $max_tokens_ph = ($model[5] === null) ? 'NULL' : '%d';
        $dimensions_ph = ($model[6] === null) ? 'NULL' : '%d';

        $params = array($model[0], $model[1], $model[2], $model[3], $model[4]);
        if ($model[5] !== null) { $params[] = (int) $model[5]; }
        if ($model[6] !== null) { $params[] = (int) $model[6]; }
        $params[] = (int) $model[7];
        $params[] = (int) $model[8];
        $params[] = (int) $model[9];

        
        $wpdb->query($wpdb->prepare(
            "INSERT IGNORE INTO {$table_name}
                (provider, model_id, model_name, model_type, description, max_tokens, dimensions, is_recommended, is_active, sort_order)
             VALUES (%s, %s, %s, %s, %s, {$max_tokens_ph}, {$dimensions_ph}, %d, %d, %d)",
            $params
        ));
    }
}

function aieo_create_product_recommendations_table() {
    global $wpdb;

    $table_name = $wpdb->prefix . 'aieo_product_recommendations';
    $charset_collate = aieo_get_charset_collate();

    
    $table_exists = $wpdb->get_var( "SHOW TABLES LIKE '{$table_name}'" ) === $table_name;
    if ( $table_exists ) {
        return false;
    }

    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        `product_id` bigint(20) UNSIGNED NOT NULL,
        `relation_type` varchar(50) NOT NULL,
        `recommended_products` longtext DEFAULT NULL,
        `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        `expires_at` datetime DEFAULT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `idx_product_relation` (`product_id`, `relation_type`),
        KEY `idx_relation_type` (`relation_type`),
        KEY `idx_expires_at` (`expires_at`)
    ) $charset_collate;";

    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql );

    return true;
}

function aieo_create_reco_reverse_refs_table() {
    global $wpdb;

    $table_name = $wpdb->prefix . 'aieo_reco_reverse_refs';
    $charset_collate = aieo_get_charset_collate();

    
    $table_exists = $wpdb->get_var( "SHOW TABLES LIKE '{$table_name}'" ) === $table_name;
    if ( $table_exists ) {
        return false;
    }

    
    
    
    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        `referenced_product_id` BIGINT(20) UNSIGNED NOT NULL,
        `cache_entry_id`        BIGINT(20) UNSIGNED NOT NULL,
        PRIMARY KEY (`referenced_product_id`, `cache_entry_id`),
        KEY `idx_cache_entry` (`cache_entry_id`)
    ) {$charset_collate};";

    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql );

    return true;
}

function aieo_create_product_need_suggestions_table() {
    global $wpdb;

    $table_name = $wpdb->prefix . 'aieo_product_need_suggestions';
    $charset_collate = aieo_get_charset_collate();

    
    $table_exists = $wpdb->get_var( "SHOW TABLES LIKE '{$table_name}'" ) === $table_name;
    if ( $table_exists ) {
        return false;
    }

    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        `product_id` bigint(20) UNSIGNED NOT NULL COMMENT 'Source product ID',
        `suggested_products` text DEFAULT NULL COMMENT 'JSON array of up to 6 product IDs sorted by sales',
        `match_score` int(11) DEFAULT 0 COMMENT 'Total needs match count for best suggestion',
        `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        UNIQUE KEY `idx_product_id` (`product_id`)
    ) $charset_collate;";

    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql );

    return true;
}

