<?php
/**
 * Flow Logic Trait.
 *
 * @package automation-web-platform
 */

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

/**
 * Trait WAWP_Flow_Logic
 */
trait WAWP_Flow_Logic {
	use WAWP_Settings_Handler;
	use WAWP_Security_Trait;

	/**
	 * Get available placeholders for notifications.
	 *
	 * @return array
	 */
	public function get_available_placeholders() {
		static $cache = null;
		if ( null !== $cache ) {
			return $cache;
		}

		// ── General Site Variables (always available for every trigger) ──
		$general = array(
			'{{sitename}}'          => esc_html__( 'Site Title', 'automation-web-platform' ),
			'{{siteurl}}'           => esc_html__( 'Home URL', 'automation-web-platform' ),
			'{{tagline}}'           => esc_html__( 'Site Tagline', 'automation-web-platform' ),
			'{{privacy-policy}}'    => esc_html__( 'Privacy Policy URL', 'automation-web-platform' ),
			'{{current_date_time}}' => esc_html__( 'Current Date/Time', 'automation-web-platform' ),
			'{{currency}}'          => esc_html__( 'Currency Symbol', 'automation-web-platform' ),
		);

		// ── WordPress User Placeholders (user_login / user_signup) ──
		$wp_user = array(
			'{{wp-display-name}}' => esc_html__( 'Display Name', 'automation-web-platform' ),
			'{{wp-first-name}}'   => esc_html__( 'First Name', 'automation-web-platform' ),
			'{{wp-last-name}}'    => esc_html__( 'Last Name', 'automation-web-platform' ),
			'{{wp-username}}'     => esc_html__( 'Username', 'automation-web-platform' ),
			'{{wp-email}}'        => esc_html__( 'User Email', 'automation-web-platform' ),
			'{{wp-nickname}}'     => esc_html__( 'User Nickname', 'automation-web-platform' ),
			'{{wp-user-website}}' => esc_html__( 'User Website URL', 'automation-web-platform' ),
			'{{wp-user-bio}}'     => esc_html__( 'User Bio', 'automation-web-platform' ),
			'{{wawp_user_phone}}' => esc_html__( 'User Phone Number', 'automation-web-platform' ),
			'{{user_name}}'       => esc_html__( 'Username (Legacy)', 'automation-web-platform' ),
			'{{display_name}}'    => esc_html__( 'Display Name (Legacy)', 'automation-web-platform' ),
			'{{user_email}}'      => esc_html__( 'User Email (Legacy)', 'automation-web-platform' ),
		);

		// ── WooCommerce Order Placeholders ──
		$wc_order = array(
			'{{order_id}}'               => esc_html__( 'Order ID', 'automation-web-platform' ),
			'{{wc-order}}'               => esc_html__( 'Order Number', 'automation-web-platform' ),
			'{{order_date}}'             => esc_html__( 'Order Date', 'automation-web-platform' ),
			'{{status}}'                 => esc_html__( 'Order Status', 'automation-web-platform' ),
			'{{order_status_label}}'     => esc_html__( 'New Status Label', 'automation-web-platform' ),
			'{{old_order_status_label}}' => esc_html__( 'Previous Status Label', 'automation-web-platform' ),
			'{{order_total}}'            => esc_html__( 'Grand Total', 'automation-web-platform' ),
			'{{order_subtotal}}'         => esc_html__( 'Subtotal', 'automation-web-platform' ),
			'{{order_discount}}'         => esc_html__( 'Discount Amount', 'automation-web-platform' ),
			'{{order_tax}}'              => esc_html__( 'Tax Amount', 'automation-web-platform' ),
			'{{order_shipping}}'         => esc_html__( 'Shipping Cost', 'automation-web-platform' ),
			'{{order_link}}'             => esc_html__( 'View Order Link (Customer)', 'automation-web-platform' ),
			'{{admin_order_link}}'       => esc_html__( 'View Order Link (Admin WP)', 'automation-web-platform' ),
			'{{cust_note}}'              => esc_html__( 'Customer Note', 'automation-web-platform' ),
			'{{payment_method_title}}'   => esc_html__( 'Payment Method', 'automation-web-platform' ),
			'{{transaction_id}}'         => esc_html__( 'Transaction ID', 'automation-web-platform' ),
			'{{shipping_method_title}}'  => esc_html__( 'Shipping Method', 'automation-web-platform' ),
			'{{customer_name}}'          => esc_html__( 'Customer Full Name', 'automation-web-platform' ),
		);

		// ── WooCommerce Items & Products ──
		$wc_items = array(
			'{{product}}'                 => esc_html__( 'Detailed Items (List)', 'automation-web-platform' ),
			'{{wc-product-names}}'        => esc_html__( 'Product Names (Comma)', 'automation-web-platform' ),
			'{{wc-product-names-br}}'     => esc_html__( 'Product Names (New Line)', 'automation-web-platform' ),
			'{{wc-product-name-count}}'   => esc_html__( 'Name Summary', 'automation-web-platform' ),
			'{{wc-order-items-br}}'       => esc_html__( 'Items with Qty (Name × Qty)', 'automation-web-platform' ),
			'{{wc-order-items-price}}'    => esc_html__( 'Items with Qty + Price', 'automation-web-platform' ),
			'{{wc-product-link}}'         => esc_html__( 'Product Links (Comma)', 'automation-web-platform' ),
			'{{wc-product-name-link}}'    => esc_html__( 'Product Name + Link (Comma)', 'automation-web-platform' ),
			'{{wc-product-name-link-br}}' => esc_html__( 'Product Name + Link (New Line)', 'automation-web-platform' ),
			'{{wc-sku}}'                  => esc_html__( 'Product SKUs (Comma)', 'automation-web-platform' ),
			'{{wc-total-products}}'       => esc_html__( 'Unique Products Count', 'automation-web-platform' ),
			'{{wc-total-items}}'          => esc_html__( 'Total Items Quantity', 'automation-web-platform' ),
		);

		// ── WooCommerce Billing & Shipping ──
		$wc_address = array(
			'{{billing_first_name}}'  => esc_html__( 'Billing First Name', 'automation-web-platform' ),
			'{{billing_last_name}}'   => esc_html__( 'Billing Last Name', 'automation-web-platform' ),
			'{{billing_email}}'       => esc_html__( 'Billing Email', 'automation-web-platform' ),
			'{{billing_phone}}'       => esc_html__( 'Billing Phone', 'automation-web-platform' ),
			'{{billing_address_1}}'   => esc_html__( 'Billing Address 1', 'automation-web-platform' ),
			'{{billing_address_2}}'   => esc_html__( 'Billing Address 2', 'automation-web-platform' ),
			'{{billing_city}}'        => esc_html__( 'Billing City', 'automation-web-platform' ),
			'{{billing_postcode}}'    => esc_html__( 'Billing Postcode', 'automation-web-platform' ),
			'{{billing_state}}'       => esc_html__( 'Billing State', 'automation-web-platform' ),
			'{{billing_country}}'     => esc_html__( 'Billing Country', 'automation-web-platform' ),
			'{{shipping_first_name}}' => esc_html__( 'Shipping First Name', 'automation-web-platform' ),
			'{{shipping_last_name}}'  => esc_html__( 'Shipping Last Name', 'automation-web-platform' ),
			'{{shipping_address_1}}'  => esc_html__( 'Shipping Address 1', 'automation-web-platform' ),
			'{{shipping_address_2}}'  => esc_html__( 'Shipping Address 2', 'automation-web-platform' ),
			'{{shipping_city}}'       => esc_html__( 'Shipping City', 'automation-web-platform' ),
			'{{shipping_postcode}}'   => esc_html__( 'Shipping Postcode', 'automation-web-platform' ),
			'{{shipping_state}}'      => esc_html__( 'Shipping State', 'automation-web-platform' ),
			'{{shipping_country}}'    => esc_html__( 'Shipping Country', 'automation-web-platform' ),
		);

		// ── Shipping Carrier / Tracking (custom order meta — common keys used by shipping plugins) ──
		$wc_shipping_tracking = array(
			'{{_wc_shipment_tracking_items}}' => esc_html__( 'Shipment Tracking (All)', 'automation-web-platform' ),
			'{{_tracking_number}}'            => esc_html__( 'Tracking Number', 'automation-web-platform' ),
			'{{_tracking_provider}}'          => esc_html__( 'Tracking Provider / Carrier', 'automation-web-platform' ),
			'{{_tracking_link}}'              => esc_html__( 'Tracking Link URL', 'automation-web-platform' ),
			'{{_date_shipped}}'               => esc_html__( 'Date Shipped', 'automation-web-platform' ),
		);

		// ── Abandoned Cart Placeholders ──
		$abandoned_cart = array(
			'{{first_name}}'                => esc_html__( 'Customer First Name', 'automation-web-platform' ),
			'{{last_name}}'                 => esc_html__( 'Customer Last Name', 'automation-web-platform' ),
			'{{name}}'                      => esc_html__( 'Customer Full Name', 'automation-web-platform' ),
			'{{email}}'                     => esc_html__( 'Customer Email', 'automation-web-platform' ),
			'{{phone_number}}'              => esc_html__( 'Customer Phone', 'automation-web-platform' ),
			'{{cart_total}}'                => esc_html__( 'Cart Total', 'automation-web-platform' ),
			'{{product_in_cart_list}}'      => esc_html__( 'Cart Products (Text List)', 'automation-web-platform' ),
			'{{product_in_cart_list_html}}' => esc_html__( 'Cart Products (HTML)', 'automation-web-platform' ),
			'{{coupon_code}}'               => esc_html__( 'Generated Coupon Code', 'automation-web-platform' ),
			'{{cart_recovery_url}}'         => esc_html__( 'Cart Recovery Link', 'automation-web-platform' ),
			'{{cart_id}}'                   => esc_html__( 'Cart ID', 'automation-web-platform' ),
			'{{billing_country}}'           => esc_html__( 'Billing Country', 'automation-web-platform' ),
			'{{billing_city}}'              => esc_html__( 'Billing City', 'automation-web-platform' ),
			'{{billing_address_1}}'         => esc_html__( 'Billing Address', 'automation-web-platform' ),
		);

		// ── WooCommerce Subscriptions ──
		$wc_subscriptions = array();
		if ( class_exists( 'WooCommerce' ) && class_exists( 'WC_Subscriptions' ) && $this->is_subscription_notifs_active() ) {
			$wc_subscriptions = array(
				'{{subscription_id}}'           => esc_html__( 'Subscription ID', 'automation-web-platform' ),
				'{{subscription_total}}'        => esc_html__( 'Subscription Total', 'automation-web-platform' ),
				'{{subscription_status_label}}' => esc_html__( 'Subscription Status', 'automation-web-platform' ),
				'{{next_payment_date}}'         => esc_html__( 'Next Payment Date', 'automation-web-platform' ),
				'{{renewal_order_id}}'          => esc_html__( 'Renewal Order ID', 'automation-web-platform' ),
				'{{renewal_amount}}'            => esc_html__( 'Renewal Amount', 'automation-web-platform' ),
			);
		}

		// Flat list for backward-compat (used by old callers).
		$placeholders = array_merge(
			$general,
			$wp_user,
			$wc_order,
			$wc_items,
			$wc_address,
			$wc_shipping_tracking,
			$abandoned_cart,
			$wc_subscriptions
		);

		$cache = apply_filters(
			'wawp_notif_available_placeholders',
			$placeholders,
			array(
				'general'              => $general,
				'wp_user'              => $wp_user,
				'wc_order'             => $wc_order,
				'wc_items'             => $wc_items,
				'wc_address'           => $wc_address,
				'wc_shipping_tracking' => $wc_shipping_tracking,
				'abandoned_cart'       => $abandoned_cart,
				'wc_subscriptions'     => $wc_subscriptions,
			)
		);

		return $cache;
	}

	/**
	 * Get placeholders grouped by category, used by the React Personalization panel.
	 * Groups are ordered: trigger-specific first, then general last.
	 *
	 * @param string $trigger_key The flow trigger key.
	 * @return array<string, array<string,string>>
	 */
	public function get_grouped_placeholders( $trigger_key = '' ) {
		// Refresh flat cache so the named groups param is populated.
		static $groups_cache = null;

		if ( null === $groups_cache ) {
			// Re-run with both params captured via filter.
			$groups_cache = array();
			add_filter(
				'wawp_notif_available_placeholders',
				function ( $flat, $groups = array() ) use ( &$groups_cache ) {
					$groups_cache = $groups;
					return $flat;
				},
				PHP_INT_MAX,
				2
			);
			$this->get_available_placeholders();
		}

		$is_wp   = str_starts_with( $trigger_key, 'user_' );
		$is_wc   = str_contains( $trigger_key, 'wc_status_' ) || str_contains( $trigger_key, 'wc_order_' );
		$is_sub  = str_contains( $trigger_key, 'wc_sub_' );
		$is_cart = in_array( $trigger_key, array( 'abandoned_cart', 'potential_customer', 'shopping' ), true );

		$result = array();

		if ( $is_wp ) {
			$result[ esc_html__( 'WordPress User', 'automation-web-platform' ) ] = $groups_cache['wp_user'] ?? array();
		} elseif ( $is_cart ) {
			$result[ esc_html__( 'Cart & Customer', 'automation-web-platform' ) ] = $groups_cache['abandoned_cart'] ?? array();
		} elseif ( $is_sub ) {
			$result[ esc_html__( 'Order Details', 'automation-web-platform' ) ]      = $groups_cache['wc_order'] ?? array();
			$result[ esc_html__( 'Subscriptions', 'automation-web-platform' ) ]      = $groups_cache['wc_subscriptions'] ?? array();
			$result[ esc_html__( 'Products & Items', 'automation-web-platform' ) ]   = $groups_cache['wc_items'] ?? array();
			$result[ esc_html__( 'Billing & Shipping', 'automation-web-platform' ) ] = $groups_cache['wc_address'] ?? array();
			$result[ esc_html__( 'Shipping Tracking', 'automation-web-platform' ) ]  = $groups_cache['wc_shipping_tracking'] ?? array();
		} elseif ( $is_wc ) {
			$result[ esc_html__( 'Order Details', 'automation-web-platform' ) ]      = $groups_cache['wc_order'] ?? array();
			$result[ esc_html__( 'Products & Items', 'automation-web-platform' ) ]   = $groups_cache['wc_items'] ?? array();
			$result[ esc_html__( 'Billing & Shipping', 'automation-web-platform' ) ] = $groups_cache['wc_address'] ?? array();
			$result[ esc_html__( 'Shipping Tracking', 'automation-web-platform' ) ]  = $groups_cache['wc_shipping_tracking'] ?? array();
		} else {
			// Fallback: show everything.
			$result[ esc_html__( 'WordPress User', 'automation-web-platform' ) ]     = $groups_cache['wp_user'] ?? array();
			$result[ esc_html__( 'Order Details', 'automation-web-platform' ) ]      = $groups_cache['wc_order'] ?? array();
			$result[ esc_html__( 'Products & Items', 'automation-web-platform' ) ]   = $groups_cache['wc_items'] ?? array();
			$result[ esc_html__( 'Billing & Shipping', 'automation-web-platform' ) ] = $groups_cache['wc_address'] ?? array();
		}

		// General site variables are always last.
		$result[ esc_html__( 'General Site Variables', 'automation-web-platform' ) ] = $groups_cache['general'] ?? array();

		return array_filter( $result, fn( $group ) => ! empty( $group ) );
	}

	/**
	 * Check if subscription notifications are active.
	 *
	 * @return bool
	 */
	private function is_subscription_notifs_active() {
		if ( ! class_exists( 'WC_Subscriptions' ) ) {
			return false;
		}

		$enabled = 1 === (int) $this->get_wawp_setting( WAWP_Settings_Registry::SUB_NOTIFS_ENABLED, 0 );

		return (bool) apply_filters( 'wawp_notif_subscriptions_enabled', $enabled );
	}

	/**
	 * Get available notification triggers.
	 *
	 * @return array
	 */
	public function get_available_triggers() {
		static $cache = null;
		if ( null !== $cache ) {
			return $cache;
		}

		$triggers = array(
			'user_login'         => array(
				'label'     => esc_html__( 'User Login', 'automation-web-platform' ),
				'icon_file' => 'wordpress.svg',
			),
			'user_signup'        => array(
				'label'     => esc_html__( 'User Signup', 'automation-web-platform' ),
				'icon_file' => 'wordpress.svg',
			),
			'abandoned_cart'     => array(
				'label'     => esc_html__( 'Wawp: Abandoned Cart', 'automation-web-platform' ),
				'icon_file' => 'wawp.svg',
			),
			'potential_customer' => array(
				'label'     => esc_html__( 'Wawp: Potential Customer', 'automation-web-platform' ),
				'icon_file' => 'wawp.svg',
			),
			'shopping'           => array(
				'label'     => esc_html__( 'Wawp: Customer Shopping', 'automation-web-platform' ),
				'icon_file' => 'wawp.svg',
			),
		);

		if ( class_exists( 'WooCommerce' ) ) {
			foreach ( wc_get_order_statuses() as $slug => $status_label ) {
				$key              = 'wc_status_' . str_replace( 'wc-', '', $slug );
				$triggers[ $key ] = array(
					/* translators: %s: The order status label (e.g. Processing) */
					'label'     => sprintf( esc_html__( 'Order Status: %s', 'automation-web-platform' ), $status_label ),
					'icon_file' => 'woocommerce.svg',
				);
			}

			$triggers['wc_order_note_added'] = array(
				'label'     => esc_html__( 'Order: Note to Customer Added', 'automation-web-platform' ),
				'icon_file' => 'woocommerce.svg',
			);

			if ( class_exists( 'WC_Subscriptions' ) && $this->is_subscription_notifs_active() ) {
				if ( function_exists( 'wcs_get_subscription_statuses' ) ) {
					foreach ( wcs_get_subscription_statuses() as $slug => $status_label ) {
						$key              = 'wc_status_' . str_replace( 'wc-', '', $slug );
						$triggers[ $key ] = array(
							/* translators: %s: The subscription status label (e.g. Active) */
							'label'     => sprintf( esc_html__( 'Subscription Status: %s', 'automation-web-platform' ), $status_label ),
							'icon_file' => 'Woo-Subscriptions.svg',
						);
					}
				}

				$triggers['wc_sub_renewal_payment_complete'] = array(
					'label'     => esc_html__( 'Subscription Renewal Payment Complete', 'automation-web-platform' ),
					'icon_file' => 'Woo-Subscriptions.svg',
				);
				$triggers['wc_sub_note_added']               = array(
					'label'     => esc_html__( 'Subscription: Note to Customer Added', 'automation-web-platform' ),
					'icon_file' => 'Woo-Subscriptions.svg',
				);
			}
		}

		$cache = apply_filters( 'wawp_notif_available_triggers', $triggers );

		return $cache;
	}

	/**
	 * Get language details by code.
	 *
	 * @param string $lang_code Language code.
	 * @return array|null
	 */
	private function get_language_details( $lang_code ) {
		if ( ! function_exists( 'wp_get_available_translations' ) ) {
			require_once ABSPATH . 'wp-admin/includes/translation-install.php';
		}

		$trans = wp_get_available_translations();
		if ( isset( $trans[ $lang_code ] ) ) {
			return $trans[ $lang_code ];
		}

		if ( 'en_US' === $lang_code ) {
			return array(
				'language'     => 'en_US',
				'native_name'  => 'English',
				'english_name' => 'English (US)',
			);
		}

		return null;
	}

	/**
	 * Clear scheduled crons for a specific rule.
	 *
	 * @param string $rule_id Rule name/ID.
	 */
	private function clear_crons_for_rule( $rule_id ) {
		$deleted = 0;
		$crons   = _get_cron_array();
		if ( empty( $crons ) ) {
			return;
		}

		foreach ( $crons as $timestamp => $hooks ) {
			if ( isset( $hooks[ WAWP_NOTIF_CRON_HOOK ] ) ) {
				foreach ( $hooks[ WAWP_NOTIF_CRON_HOOK ] as $key => $details ) {
					if ( ! empty( $details['args'][0]['rule_internal_id'] ) && $details['args'][0]['rule_internal_id'] === $rule_id ) {
						wp_unschedule_event( $timestamp, WAWP_NOTIF_CRON_HOOK, $details['args'] );
						++$deleted;
					}
				}
			}
		}
	}

	/**
	 * Get global notification settings with legacy migration support.
	 *
	 * @return array
	 */
	public function get_global_settings() {
		static $cache = null;
		if ( null !== $cache ) {
			return $cache;
		}

		$selected_instance_ids       = $this->get_wawp_setting( WAWP_Settings_Registry::NOTIF_SELECTED_INSTANCE_IDS, null );
		$admin_selected_instance_ids = $this->get_wawp_setting( WAWP_Settings_Registry::NOTIF_ADMIN_SELECTED_INSTANCE_IDS, null );

		// Fast path: use options if they exist.
		if ( null !== $selected_instance_ids ) {
			$cache = array(
				'selected_instance_ids'       => $selected_instance_ids,
				'admin_selected_instance_ids' => $admin_selected_instance_ids ? $admin_selected_instance_ids : '',
			);

			return $cache;
		}

		// Migration path: if option missing, check old table.
		$db    = WAWP()->db;
		$table = $db->wpdb->prefix . 'wawp_notif_global';
		if ( $db->has_table( $table ) ) {
			$cache_key = 'wawp_notif_global_migration_row';
			$results   = WAWP()->db->get_results_cached( $db->wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $table, 1 ), $cache_key, 'wawp_notif', HOUR_IN_SECONDS );
			$row       = ! empty( $results ) ? $results[0] : null;

			if ( $row ) {
				$selected_instance_ids       = $row['selected_instance_ids'] ?? '';
				$admin_selected_instance_ids = $row['admin_selected_instance_ids'] ?? '';
				$this->update_wawp_setting( WAWP_Settings_Registry::NOTIF_SELECTED_INSTANCE_IDS, $selected_instance_ids );
				$this->update_wawp_setting( WAWP_Settings_Registry::NOTIF_ADMIN_SELECTED_INSTANCE_IDS, $admin_selected_instance_ids );
			}
		}

		$cache = array(
			'selected_instance_ids'       => $selected_instance_ids ? $selected_instance_ids : '',
			'admin_selected_instance_ids' => $admin_selected_instance_ids ? $admin_selected_instance_ids : '',
		);

		return $cache;
	}

	/**
	 * Update global notification settings.
	 *
	 * @param string|null $ids_csv       Selected instance IDs (CSV).
	 * @param string      $admin_ids_csv Selected admin instance IDs (CSV).
	 */
	public function set_global_settings( $ids_csv, $admin_ids_csv = '' ) {
		if ( null !== $ids_csv ) {
			$this->update_wawp_setting( WAWP_Settings_Registry::NOTIF_SELECTED_INSTANCE_IDS, $ids_csv );
		}

		if ( null !== $admin_ids_csv ) {
			$this->update_wawp_setting( WAWP_Settings_Registry::NOTIF_ADMIN_SELECTED_INSTANCE_IDS, $admin_ids_csv );
		}
	}

	/**
	 * Settings cache.
	 *
	 * @var array|null
	 */
	private static $settings_cache = null;

	/**
	 * Get sent counts for all notification rules.
	 *
	 * @return array Mapping of rule_id => sent_count.
	 */
	public function get_all_rules_sent_counts() {
		$counts = wp_cache_get( 'wawp_all_rules_sent_counts_map', 'wawp_logs' );
		if ( is_array( $counts ) ) {
			return $counts;
		}

		global $wpdb;
		$table  = WAWP()->db->get_unified_table_name();
		$counts = array();

		// Safely fetch details for all logged messages that could contain rule_id.
		$results = WAWP()->db->get_results_cached(
			$wpdb->prepare( 'SELECT details FROM %i WHERE details LIKE %s', $table, '%"rule_id"%' ),
			'wawp_all_rules_sent_counts_results',
			'wawp_logs',
			60
		);
		if ( ! empty( $results ) ) {
			foreach ( $results as $row ) {
				$details = json_decode( $row['details'], true );
				if ( ! empty( $details['rule_id'] ) ) {
					$rid            = (int) $details['rule_id'];
					$counts[ $rid ] = ( isset( $counts[ $rid ] ) ? $counts[ $rid ] + 1 : 1 );
				}
			}
		}

		wp_cache_set( 'wawp_all_rules_sent_counts_map', $counts, 'wawp_logs', 60 );
		return $counts;
	}

	/**
	 * Get all notification settings.
	 *
	 * @return array
	 */
	public function get_settings() {
		$cache_key = 'wawp_notification_settings_all_v2';
		$cached    = wp_cache_get( $cache_key, 'wawp_notif' );
		if ( is_array( $cached ) ) {
			// Overlay sent counts on the cached settings!
			$sent_counts = $this->get_all_rules_sent_counts();
			if ( ! empty( $cached['configured_languages'] ) ) {
				foreach ( $cached['configured_languages'] as $lang_code => &$lang_data ) {
					if ( ! empty( $lang_data['notifications'] ) ) {
						foreach ( $lang_data['notifications'] as &$r ) {
							$r['sent_count'] = isset( $sent_counts[ $r['id'] ] ) ? (int) $sent_counts[ $r['id'] ] : 0;
						}
					}
				}
			}
			return $cached;
		}

		if ( null !== self::$settings_cache ) {
			// Overlay sent counts on memory cached settings!
			$sent_counts = $this->get_all_rules_sent_counts();
			if ( ! empty( self::$settings_cache['configured_languages'] ) ) {
				foreach ( self::$settings_cache['configured_languages'] as $lang_code => &$lang_data ) {
					if ( ! empty( $lang_data['notifications'] ) ) {
						foreach ( $lang_data['notifications'] as &$r ) {
							$r['sent_count'] = isset( $sent_counts[ $r['id'] ] ) ? (int) $sent_counts[ $r['id'] ] : 0;
						}
					}
				}
			}
			return self::$settings_cache;
		}

		if ( empty( $this->wawp_db_manager ) ) {
			return array(
				'selected_instance_ids' => '',
				'main_language_code'    => $this->get_wawp_setting( 'WPLANG', 'en_US' ) ? $this->get_wawp_setting( 'WPLANG', 'en_US' ) : 'en_US',
				'configured_languages'  => array(),
			);
		}

		$sent_counts = $this->get_all_rules_sent_counts();
		$global      = $this->get_global_settings();
		$settings    = array(
			'selected_instance_ids'       => isset( $global['selected_instance_ids'] ) ? $global['selected_instance_ids'] : '',
			'admin_selected_instance_ids' => isset( $global['admin_selected_instance_ids'] ) ? $global['admin_selected_instance_ids'] : '',
			'main_language_code'          => '',
			'configured_languages'        => array(),
		);

		$lang_rows = WAWP()->notification_manager->get_languages();
		if ( empty( $lang_rows ) ) {
			$ml  = get_locale();
			$det = $this->get_language_details( $ml );
			$nm  = $det ? $det['native_name'] . ' (' . $det['english_name'] . ')' : $ml;
			WAWP()->notification_manager->upsert_language( $ml, $nm, 1 );
			$lang_rows = WAWP()->notification_manager->get_languages();
		}

		foreach ( $lang_rows as $row ) {
			$settings['configured_languages'][ $row['code'] ] = array(
				'name'          => $row['name'],
				'notifications' => array(),
			);
			if ( 1 === (int) $row['is_main'] ) {
				$settings['main_language_code'] = $row['code'];
			}
		}

		if ( empty( $settings['main_language_code'] ) ) {
			$lang_keys                      = array_keys( $settings['configured_languages'] );
			$settings['main_language_code'] = ! empty( $lang_keys ) ? reset( $lang_keys ) : '';
		}

		global $wpdb;
		$db = WAWP()->db;

		$flows = $db->get_results_cached( $wpdb->prepare( 'SELECT * FROM %i', $this->wawp_db_manager->tables['flows'] ), 'wawp_flows_all', 'wawp_db' );
		foreach ( $flows as $f ) {
			$flow_id = $f['id'];
			$step    = $db->get_results_cached( $wpdb->prepare( "SELECT * FROM %i WHERE flow_id = %d AND step_type = 'notification' ORDER BY priority ASC LIMIT 1", $this->wawp_db_manager->tables['flow_steps'], $flow_id ), 'wawp_flow_step_' . $flow_id, 'wawp_db' );
			$step    = ! empty( $step ) ? $step[0] : null;

			if ( ! $step ) {
				continue;
			}
			$step_id   = $step['id'];
			$meta_rows = $db->get_results_cached( $wpdb->prepare( 'SELECT config_key, config_value FROM %i WHERE step_id = %d', $this->wawp_db_manager->tables['flow_step_meta'], $step_id ), 'wawp_flow_meta_' . $step_id, 'wawp_db' );
			$meta      = array();
			foreach ( $meta_rows as $mr ) {
				$meta[ $mr['config_key'] ] = $this->from_db( $mr['config_value'] );
			}

			foreach ( $settings['configured_languages'] as $lang_code => $lang_data ) {
				$trans_rows = $db->get_results_cached( $wpdb->prepare( 'SELECT config_key, translation_value FROM %i WHERE step_id = %d AND language_code = %s', $this->wawp_db_manager->tables['flow_translations'], $step_id, $lang_code ), 'wawp_flow_trans_' . $step_id . '_' . $lang_code, 'wawp_db' );
				if ( empty( $trans_rows ) ) {
					continue;
				}
				$trans = array();
				foreach ( $trans_rows as $tr ) {
					$trans[ $tr['config_key'] ] = $this->from_db( $tr['translation_value'] );
				}

				$normalized = array(
					'id'                          => $flow_id,
					'name'                        => $f['name'],
					'trigger_key'                 => $f['trigger_key'],
					'enabled'                     => ( isset( $f['status'] ) && 'active' === $f['status'] ) ? 1 : 0,
					'sender_type'                 => isset( $meta['sender_type'] ) ? $meta['sender_type'] : 'user_whatsapp',
					'whatsapp_enabled'            => ! empty( $meta['whatsapp_enabled'] ) ? 1 : 0,
					'whatsapp_message'            => isset( $trans['whatsapp_message'] ) ? $trans['whatsapp_message'] : '',
					'whatsapp_media_url'          => isset( $trans['whatsapp_media_url'] ) ? $trans['whatsapp_media_url'] : '',
					'email_enabled'               => ! empty( $meta['email_enabled'] ) ? 1 : 0,
					'email_subject'               => isset( $trans['email_subject'] ) ? $trans['email_subject'] : '',
					'email_body'                  => isset( $trans['email_body'] ) ? $trans['email_body'] : '',
					'admin_user_ids'              => isset( $meta['admin_user_ids'] ) ? $meta['admin_user_ids'] : '',
					'admin_whatsapp_enabled'      => ! empty( $meta['admin_whatsapp_enabled'] ) ? 1 : 0,
					'admin_whatsapp_message'      => isset( $trans['admin_whatsapp_message'] ) ? $trans['admin_whatsapp_message'] : '',
					'admin_whatsapp_media_url'    => isset( $trans['admin_whatsapp_media_url'] ) ? $trans['admin_whatsapp_media_url'] : '',
					'admin_email_enabled'         => ! empty( $meta['admin_email_enabled'] ) ? 1 : 0,
					'admin_email_subject'         => isset( $trans['admin_email_subject'] ) ? $trans['admin_email_subject'] : '',
					'admin_email_body'            => isset( $trans['admin_email_body'] ) ? $trans['admin_email_body'] : '',
					'meta_message_type'           => isset( $meta['meta_message_type'] ) ? $meta['meta_message_type'] : 'text',
					'meta_template_name'          => isset( $meta['meta_template_name'] ) ? $meta['meta_template_name'] : '',
					'meta_template_lang'          => isset( $meta['meta_template_lang'] ) ? $meta['meta_template_lang'] : '',
					'country_filter_enabled'      => ! empty( $meta['country_filter_enabled'] ) ? 1 : 0,
					'product_filter_enabled'      => ! empty( $meta['product_filter_enabled'] ) ? 1 : 0,
					'category_filter_enabled'     => ! empty( $meta['category_filter_enabled'] ) ? 1 : 0,
					'payment_filter_enabled'      => ! empty( $meta['payment_filter_enabled'] ) ? 1 : 0,
					'billing_countries_whitelist' => isset( $meta['billing_countries_whitelist'] ) ? $meta['billing_countries_whitelist'] : '',
					'billing_countries_blocklist' => isset( $meta['billing_countries_blocklist'] ) ? $meta['billing_countries_blocklist'] : '',
					'payment_gateways'            => isset( $meta['payment_gateways'] ) ? $meta['payment_gateways'] : '',
					'product_ids_whitelist'       => isset( $meta['product_ids_whitelist'] ) ? $meta['product_ids_whitelist'] : '',
					'product_ids_blocklist'       => isset( $meta['product_ids_blocklist'] ) ? $meta['product_ids_blocklist'] : '',
					'category_ids_whitelist'      => isset( $meta['category_ids_whitelist'] ) ? $meta['category_ids_whitelist'] : '',
					'category_ids_blocklist'      => isset( $meta['category_ids_blocklist'] ) ? $meta['category_ids_blocklist'] : '',
					'image_source_type'           => isset( $meta['image_source_type'] ) ? $meta['image_source_type'] : 'none',
					'custom_image_url'            => isset( $meta['custom_image_url'] ) ? $meta['custom_image_url'] : '',
					'send_product_image'          => ! empty( $meta['send_product_image'] ) ? 1 : 0,
					'send_timing'                 => isset( $meta['send_timing'] ) ? $meta['send_timing'] : 'instant',
					'delay_unit'                  => isset( $meta['delay_unit'] ) ? $meta['delay_unit'] : 'minutes',
					'delay_value'                 => isset( $meta['delay_value'] ) ? $meta['delay_value'] : 0,
					'email_template_id'           => isset( $meta['email_template_id'] ) ? $meta['email_template_id'] : '',
					'admin_email_template_id'     => isset( $meta['admin_email_template_id'] ) ? $meta['admin_email_template_id'] : '',
					'sent_count'                  => isset( $sent_counts[ $flow_id ] ) ? (int) $sent_counts[ $flow_id ] : 0,
				);
				$settings['configured_languages'][ $lang_code ]['notifications'][] = $normalized;
			}
		}

		$all_pids = array();
		$all_cids = array();
		foreach ( $settings['configured_languages'] as $lang_code => $lang_data ) {
			foreach ( $lang_data['notifications'] as $r ) {
				if ( ! empty( $r['product_ids_whitelist'] ) ) {
					$all_pids = array_merge( $all_pids, is_array( $r['product_ids_whitelist'] ) ? $r['product_ids_whitelist'] : explode( ',', $r['product_ids_whitelist'] ) );
				}
				if ( ! empty( $r['product_ids_blocklist'] ) ) {
					$all_pids = array_merge( $all_pids, is_array( $r['product_ids_blocklist'] ) ? $r['product_ids_blocklist'] : explode( ',', $r['product_ids_blocklist'] ) );
				}
				if ( ! empty( $r['category_ids_whitelist'] ) ) {
					$all_cids = array_merge( $all_cids, is_array( $r['category_ids_whitelist'] ) ? $r['category_ids_whitelist'] : explode( ',', $r['category_ids_whitelist'] ) );
				}
				if ( ! empty( $r['category_ids_blocklist'] ) ) {
					$all_cids = array_merge( $all_cids, is_array( $r['category_ids_blocklist'] ) ? $r['category_ids_blocklist'] : explode( ',', $r['category_ids_blocklist'] ) );
				}
			}
		}

		$all_pids           = array_unique( array_filter( array_map( 'intval', $all_pids ) ) );
		$all_cids           = array_unique( array_filter( array_map( 'intval', $all_cids ) ) );
		$preloaded_entities = array();
		if ( ! empty( $all_pids ) && function_exists( 'wc_get_product' ) ) {
			foreach ( $all_pids as $pid ) {
				$p = wc_get_product( $pid );
				if ( $p ) {
					$preloaded_entities['product'][ $pid ] = $p->get_name() . " (ID: $pid)";
				}
			}
		}

		if ( ! empty( $all_cids ) ) {
			foreach ( $all_cids as $cid ) {
				$t = get_term( $cid, 'product_cat' );
				if ( $t ) {
					$preloaded_entities['category'][ $cid ] = $t->name . " (ID: $cid)";
				}
			}
		}

		$initial_products = array();
		if ( function_exists( 'wc_get_products' ) ) {
			$prod_list = wc_get_products(
				array(
					'status' => 'publish',
					'limit'  => 20,
				)
			);
			foreach ( $prod_list as $p ) {
				$initial_products[] = array(
					'id'   => $p->get_id(),
					'text' => $p->get_name() . ' (ID: ' . $p->get_id() . ')',
				);
			}
		}

		$initial_categories = array();
		$cat_list           = get_terms(
			array(
				'taxonomy'   => 'product_cat',
				'hide_empty' => false,
				'number'     => 20,
			)
		);
		if ( ! is_wp_error( $cat_list ) ) {
			foreach ( $cat_list as $t ) {
				$initial_categories[] = array(
					'id'   => $t->term_id,
					'text' => $t->name . ' (ID: ' . $t->term_id . ')',
				);
			}
		}

		self::$settings_cache = array_merge(
			$settings,
			array(
				'preloaded_entities' => $preloaded_entities,
				'initial_products'   => $initial_products,
				'initial_categories' => $initial_categories,
			)
		);

		wp_cache_set( $cache_key, self::$settings_cache, 'wawp_notif', HOUR_IN_SECONDS );

		return self::$settings_cache;
	}

	/**
	 * Get active notification statuses.
	 *
	 * @return array
	 */
	public function get_active_notification_statuses() {
		$settings        = $this->get_settings();
		$active_statuses = array();
		if ( empty( $settings['configured_languages'] ) ) {
			return array();
		}

		foreach ( $settings['configured_languages'] as $lang_code => $lang_data ) {
			if ( empty( $lang_data['notifications'] ) ) {
				continue;
			}

			foreach ( $lang_data['notifications'] as $rule ) {
				if ( ! empty( $rule['enabled'] ) && strpos( $rule['trigger_key'], 'wc_status_' ) === 0 ) {
					$status                     = str_replace( 'wc_status_', '', $rule['trigger_key'] );
					$active_statuses[ $status ] = true;
				}
			}
		}

		return array_keys( $active_statuses );
	}

	/**
	 * Get all active/enabled notifications configured in settings.
	 *
	 * @return array
	 */
	public function get_all_notifications() {
		$settings      = $this->get_settings();
		$notifications = array();
		if ( empty( $settings['configured_languages'] ) ) {
			return array();
		}

		$seen_ids = array();
		foreach ( $settings['configured_languages'] as $lang_code => $lang_data ) {
			if ( empty( $lang_data['notifications'] ) ) {
				continue;
			}

			foreach ( $lang_data['notifications'] as $rule ) {
				if ( ! empty( $rule['enabled'] ) ) {
					$id = $rule['id'];
					if ( ! in_array( $id, $seen_ids, true ) ) {
						$seen_ids[]      = $id;
						$notifications[] = $rule;
					}
				}
			}
		}

		return $notifications;
	}

	/**
	 * Get a rule by its ID.
	 *
	 * @param array  $settings    Settings array.
	 * @param string $user_locale User locale.
	 * @param int    $rule_id     Rule ID.
	 * @return array|null
	 */
	public function get_rule_by_id( $settings, $user_locale, $rule_id ) {
		$main_lang  = isset( $settings['main_language_code'] ) ? $settings['main_language_code'] : 'en_US';
		$configured = isset( $settings['configured_languages'] ) ? $settings['configured_languages'] : array();

		if ( ! $this->is_multi_lang_active() ) {
			$user_locale = $main_lang;
		}

		$to_check = array();
		if ( $user_locale !== $main_lang && isset( $configured[ $user_locale ] ) ) {
			$to_check[] = $user_locale;
		}
		if ( isset( $configured[ $main_lang ] ) ) {
			$to_check[] = $main_lang;
		}
		if ( empty( $to_check ) && ! empty( $configured ) ) {
			$to_check[] = array_key_first( $configured );
		}

		foreach ( $to_check as $lang ) {
			$notifs = isset( $configured[ $lang ]['notifications'] ) ? $configured[ $lang ]['notifications'] : array();
			foreach ( $notifs as $rule ) {
				if ( (string) $rule['id'] === (string) $rule_id ) {
					return $rule;
				}
			}
		}

		return null;
	}

	/**
	 * Get matching rules for a trigger.
	 *
	 * @param array  $settings    Notification settings.
	 * @param string $user_locale User locale.
	 * @param string $trigger_key Trigger key.
	 * @return array
	 */
	private function get_matching_rules( $settings, $user_locale, $trigger_key ) {
		$main_lang           = isset( $settings['main_language_code'] ) ? $settings['main_language_code'] : 'en_US';
		$configured          = isset( $settings['configured_languages'] ) ? $settings['configured_languages'] : array();
		$multilang_is_active = $this->is_multi_lang_active();

		// When multilang is disabled, always resolve to the main language only.
		if ( ! $multilang_is_active ) {
			$user_locale = $main_lang;
		}

		// Build the priority list of languages to check.
		// 1. User's own locale (only when multilang is on and the locale has been configured).
		// 2. Main language as fallback (always configured by definition).
		$to_check = array();
		if ( $multilang_is_active && $user_locale !== $main_lang && isset( $configured[ $user_locale ] ) ) {
			$to_check[] = $user_locale;
		}
		if ( isset( $configured[ $main_lang ] ) ) {
			$to_check[] = $main_lang;
		}

		// Edge-case: configured_languages exists but main_lang key is missing — use first available.
		if ( empty( $to_check ) && ! empty( $configured ) ) {
			$to_check[] = array_key_first( $configured );
		}

		$matches  = array();
		$seen_ids = array();
		foreach ( $to_check as $lang ) {
			$notifs = isset( $configured[ $lang ]['notifications'] ) ? $configured[ $lang ]['notifications'] : array();
			foreach ( $notifs as $rule ) {
				if ( empty( $rule['enabled'] ) ) {
					continue;
				}

				if ( ! isset( $rule['trigger_key'] ) || $rule['trigger_key'] !== $trigger_key ) {
					continue;
				}

				$st                 = isset( $rule['sender_type'] ) ? $rule['sender_type'] : 'none';
				$is_valid_recipient = str_contains( $st, 'user' ) || str_contains( $st, 'admin' );
				$is_valid_channel   = str_contains( $st, 'whatsapp' ) || str_contains( $st, 'email' ) || str_contains( $st, 'meta' );
				if ( ! $is_valid_recipient || ! $is_valid_channel ) {
					continue;
				}

				$has_content = ! empty( $rule['whatsapp_message'] ) || ! empty( $rule['email_body'] ) || ! empty( $rule['meta_template_name'] );
				if ( ! $has_content ) {
					continue;
				}

				if ( isset( $rule['id'] ) && isset( $seen_ids[ $rule['id'] ] ) ) {
					continue;
				}

				$seen_ids[ $rule['id'] ] = true;
				$matches[]               = $rule;
			}

			// If we found rules in the user's own locale, do not also send the main-language version.
			if ( ! empty( $matches ) && $lang === $user_locale && $user_locale !== $main_lang ) {
				break;
			}
		}

		return $matches;
	}

	/**
	 * Schedule or send notification.
	 *
	 * @param array  $rule              Notification rule.
	 * @param array  $replacements_user Placeholders replacements.
	 * @param array  $recipient_user    Recipient details.
	 * @param string $log_base          Log context.
	 * @param int    $related_id        Related object ID.
	 * @param string $trigger_key       Trigger key.
	 */
	public function schedule_or_send( $rule, $replacements_user, $recipient_user, $log_base, $related_id = null, $trigger_key = '' ) {
		if ( false === apply_filters( 'wawp_notif_before_send', true, $rule, $recipient_user ) ) {
			wawp_log_message( $recipient_user['phone'], '', 'Suppressed (user opt-out)', array( 'status' => 'suppressed' ), $recipient_user['user_id'], 'whatsapp', $related_id );

			return;
		}

		$timing     = isset( $rule['send_timing'] ) ? $rule['send_timing'] : 'instant';
		$delay_val  = (int) ( isset( $rule['delay_value'] ) ? $rule['delay_value'] : 0 );
		$delay_unit = isset( $rule['delay_unit'] ) ? $rule['delay_unit'] : 'minutes';
		$seconds    = 0;

		if ( 'delay' === $timing && $delay_val > 0 ) {
			switch ( $delay_unit ) {
				case 'minutes':
					$seconds = $delay_val * 60;
					break;
				case 'hours':
					$seconds = $delay_val * 3600;
					break;
				case 'days':
					$seconds = $delay_val * 86400;
					break;
			}
		}

		$args = array(
			'rule_id'          => $rule['id'],
			'lang_code'        => isset( $rule['language_code'] ) ? $rule['language_code'] : 'en_US',
			'replacements'     => $replacements_user,
			'recipient'        => $recipient_user,
			'log_base'         => $log_base,
			'related_id'       => $related_id,
			'trigger_key'      => $trigger_key,
			'rule_internal_id' => $rule['id'],
		);

		// Deliver instantly/synchronously for user login/signup or inside REST API/restricted cron contexts or manual AJAX requests to avoid delay.
		if ( 0 === $seconds && ( in_array( $trigger_key, array( 'user_login', 'user_signup' ), true ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) ) {
			$this->perform_delivery( $rule, $replacements_user, $recipient_user, $log_base, $related_id );
		} else {
			wp_schedule_single_event( time() + $seconds, WAWP_NOTIF_CRON_HOOK, array( $args ) );
		}
	}

	/**
	 * Perform delivery of notification.
	 *
	 * @param array  $rule              Notification rule.
	 * @param array  $replacements_user Placeholders replacements.
	 * @param array  $recipient_user    Recipient details.
	 * @param string $log_base          Log context.
	 * @param int    $related_id        Related object ID.
	 */
	private function perform_delivery( $rule, $replacements_user, $recipient_user, $log_base, $related_id ) {
		$context = array();
		if ( ! empty( $related_id ) ) {
			if ( class_exists( 'WooCommerce' ) ) {
				if ( ! empty( $rule['trigger_key'] ) && 0 === strpos( $rule['trigger_key'], 'wc_sub_' ) ) {
					if ( function_exists( 'wcs_get_subscription' ) ) {
						$sub = wcs_get_subscription( $related_id );
						if ( $sub ) {
							$context = $this->prepare_object_context( $sub );
						}
					}
				} else {
					$order = wc_get_order( $related_id );
					if ( $order ) {
						$context = $this->prepare_object_context( $order );
					}
				}
			}

			if ( empty( $context ) && ! empty( $rule['trigger_key'] ) && ( 'abandoned_cart' === $rule['trigger_key'] || 'potential_customer' === $rule['trigger_key'] || 'shopping' === $rule['trigger_key'] ) ) {
				$cart = WAWP()->db->get_row_by_id( 'abandoned_carts', $related_id );
				if ( $cart ) {
					$cart_contents = maybe_unserialize( $cart->cart_contents );
					if ( is_array( $cart_contents ) && ! empty( $cart_contents ) ) {
						$first_item = reset( $cart_contents );
						$pid        = $first_item['product_id'] ?? 0;
						if ( $pid ) {
							$context['product_ids'] = array( (int) $pid );
						}
					}
				}
			}
		}

		// Enforce Audience Targeting Rules before any delivery.
		if ( ! $this->evaluate_rule_filters( $rule, $context ) ) {
			return;
		}

		$this->apply_image_overrides( $rule, $context );

		// Ensure the recipient phone is populated when it was empty at scheduling time.
		if ( empty( $recipient_user['phone'] ) && ! empty( $recipient_user['user_id'] ) ) {
			$recipient_user['phone'] = WAWP()->user_manager->get_user_phone( (int) $recipient_user['user_id'] );
		}

		$sender_type = isset( $rule['sender_type'] ) ? $rule['sender_type'] : 'user_whatsapp';

		// 1. Deliver to User
		if ( str_contains( $sender_type, 'user' ) ) {
			$has_whatsapp = str_contains( $sender_type, 'whatsapp' ) || str_contains( $sender_type, 'meta' ) || str_contains( $sender_type, 'both' );
			$has_email    = str_contains( $sender_type, 'email' ) || str_contains( $sender_type, 'both' );

			if ( $has_whatsapp && ! empty( $recipient_user['phone'] ) ) {
				$wa_channel = str_contains( $sender_type, 'meta' ) ? 'user_meta' : 'user_whatsapp';
				$_media_url = isset( $rule['whatsapp_media_url'] ) ? $rule['whatsapp_media_url'] : '';
				$_media_url = is_array( $_media_url ) ? '' : (string) $_media_url;
				wawp_messenger()->send(
					$recipient_user['phone'],
					array_merge(
						$rule,
						array(
							'channel'      => $wa_channel,
							'message'      => isset( $rule['whatsapp_message'] ) ? $rule['whatsapp_message'] : '',
							'subject'      => '',
							'media_url'    => $_media_url,
							'replacements' => $replacements_user,
							'user_id'      => isset( $recipient_user['user_id'] ) ? $recipient_user['user_id'] : 0,
							'related_id'   => $related_id,
							'context'      => ( ! empty( $rule['name'] ) ? $rule['name'] : $log_base ),
						)
					)
				);
			}

			if ( $has_email && ! empty( $recipient_user['email'] ) ) {
				wawp_messenger()->send(
					$recipient_user['email'],
					array_merge(
						$rule,
						array(
							'channel'      => 'user_email',
							'message'      => ! empty( $rule['email_body'] ) ? $rule['email_body'] : ( ! empty( $rule['whatsapp_message'] ) ? $rule['whatsapp_message'] : '' ),
							'subject'      => isset( $rule['email_subject'] ) ? $rule['email_subject'] : '',
							'media_url'    => '',
							'replacements' => $replacements_user,
							'user_id'      => isset( $recipient_user['user_id'] ) ? $recipient_user['user_id'] : 0,
							'related_id'   => $related_id,
							'context'      => ( ! empty( $rule['name'] ) ? $rule['name'] : $log_base ),
						)
					)
				);
			}
		}

		// 2. Deliver to Admin
		if ( str_contains( $sender_type, 'admin' ) && ! empty( $rule['admin_user_ids'] ) ) {
			$admin_ids_val = isset( $rule['admin_user_ids'] ) ? $rule['admin_user_ids'] : '';
			$admin_ids_arr = is_array( $admin_ids_val ) ? $admin_ids_val : explode( ',', (string) $admin_ids_val );
			$admin_ids     = array_unique( array_filter( array_map( 'intval', $admin_ids_arr ) ) );
			foreach ( $admin_ids as $admin_uid ) {
				if ( (int) ( isset( $recipient_user['user_id'] ) ? $recipient_user['user_id'] : 0 ) === (int) $admin_uid ) {
					continue;
				}
				$admin = get_userdata( $admin_uid );
				if ( ! ( $admin instanceof WP_User ) ) {
					continue;
				}
				$admin_phone = WAWP()->user_manager->get_user_phone( $admin->ID );
				$rep_admin   = array_merge( $replacements_user, array( '{admin_display_name}' => $admin->display_name ) );

				$has_whatsapp = str_contains( $sender_type, 'whatsapp' ) || str_contains( $sender_type, 'meta' ) || str_contains( $sender_type, 'both' );
				$has_email    = str_contains( $sender_type, 'email' ) || str_contains( $sender_type, 'both' );

				if ( $has_whatsapp && ! empty( $admin_phone ) ) {
					$wa_channel       = str_contains( $sender_type, 'meta' ) ? 'admin_meta' : 'admin_whatsapp';
					$_admin_media_url = isset( $rule['admin_whatsapp_media_url'] ) ? $rule['admin_whatsapp_media_url'] : '';
					$_admin_media_url = is_array( $_admin_media_url ) ? '' : (string) $_admin_media_url;
					wawp_messenger()->send(
						$admin_phone,
						array_merge(
							$rule,
							array(
								'channel'      => $wa_channel,
								'message'      => isset( $rule['admin_whatsapp_message'] ) ? $rule['admin_whatsapp_message'] : '',
								'subject'      => '',
								'media_url'    => $_admin_media_url,
								'replacements' => $rep_admin,
								'user_id'      => $admin->ID,
								'related_id'   => $related_id,
								'context'      => ( ! empty( $rule['name'] ) ? $rule['name'] : $log_base ) . ' (Admin)',
							)
						)
					);
				}

				if ( $has_email && ! empty( $admin->user_email ) ) {
					wawp_messenger()->send(
						$admin->user_email,
						array_merge(
							$rule,
							array(
								'channel'      => 'admin_email',
								'message'      => ! empty( $rule['admin_email_body'] ) ? $rule['admin_email_body'] : ( ! empty( $rule['admin_whatsapp_message'] ) ? $rule['admin_whatsapp_message'] : '' ),
								'subject'      => isset( $rule['admin_email_subject'] ) ? $rule['admin_email_subject'] : '',
								'media_url'    => '',
								'replacements' => $rep_admin,
								'user_id'      => $admin->ID,
								'related_id'   => $related_id,
								'context'      => ( ! empty( $rule['name'] ) ? $rule['name'] : $log_base ) . ' (Admin)',
							)
						)
					);
				}
			}
		}
	}

	/**
	 * Search entities for UI selection.
	 *
	 * @param array $params Search parameters.
	 * @return array
	 */
	public function search_entities( $params ) {
		$type    = isset( $params['entity_type'] ) ? sanitize_text_field( $params['entity_type'] ) : 'product';
		$q       = isset( $params['q'] ) ? sanitize_text_field( $params['q'] ) : '';
		$results = array();
		if ( empty( $q ) && 'user' !== $type ) {
			return array( 'results' => array() );
		}

		switch ( $type ) {
			case 'product':
				if ( class_exists( 'WooCommerce' ) ) {
					$args     = array(
						'status' => 'publish',
						'limit'  => 20,
						's'      => $q,
					);
					$products = wc_get_products( $args );
					foreach ( $products as $p ) {
						$results[] = array(
							'id'   => $p->get_id(),
							'text' => $p->get_name() . ' (ID: ' . $p->get_id() . ')',
						);
					}
				}
				break;
			case 'category':
				if ( class_exists( 'WooCommerce' ) ) {
					$terms = get_terms(
						array(
							'taxonomy'   => 'product_cat',
							'search'     => $q,
							'hide_empty' => false,
							'number'     => 20,
						)
					);
					foreach ( $terms as $t ) {
						$results[] = array(
							'id'   => $t->term_id,
							'text' => $t->name . ' (ID: ' . $t->term_id . ')',
						);
					}
				}
				break;
			case 'user':
				if ( ! empty( $q ) ) {
					$users = array();
					if ( is_numeric( $q ) ) {
						$user_by_id = get_user_by( 'id', intval( $q ) );
						if ( $user_by_id ) {
							$users[] = $user_by_id;
						}
					}

					if ( empty( $users ) ) {
						$args_standard  = array(
							'number'         => 20,
							'search'         => '*' . esc_attr( $q ) . '*',
							'search_columns' => array( 'user_login', 'user_email', 'display_name' ),
						);
						$users_standard = get_users( $args_standard );

						// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
						$args_phone = array(
							'number'     => 20,
							'meta_query' => array(
								'relation' => 'OR',
								array(
									'key'     => 'billing_phone',
									'value'   => $q,
									'compare' => 'LIKE',
								),
								array(
									'key'     => 'phone',
									'value'   => $q,
									'compare' => 'LIKE',
								),
							),
						);
						// phpcs:enable
						$users_phone = get_users( $args_phone );

						$seen_ids = array();
						foreach ( array_merge( $users_standard, $users_phone ) as $u ) {
							if ( ! in_array( $u->ID, $seen_ids, true ) ) {
								$seen_ids[] = $u->ID;
								$users[]    = $u;
							}
						}
					}
					$users = array_slice( $users, 0, 20 );
				} else {
					$users = get_users( array( 'number' => 20 ) );
				}

				foreach ( $users as $u ) {
					$phone     = WAWP()->user_manager->get_user_phone( $u->ID );
					$phone_str = ! empty( $phone ) ? " – Phone: {$phone}" : '';
					$results[] = array(
						'id'   => $u->ID,
						'text' => "{$u->display_name} ({$u->user_email}{$phone_str} – ID: {$u->ID})",
					);
				}
				break;
		}

		return array( 'results' => $results );
	}

	/**
	 * AJAX: Search entities.
	 */
	public function ajax_search_entities() {
		$entity_type  = filter_input( INPUT_GET, 'entity_type', FILTER_SANITIZE_SPECIAL_CHARS );
		$nonce_action = ( 'user' === $entity_type ) ? 'wawp_search_users_nonce' : 'wawp_search_products_nonce';

		$this->check_permissions( $nonce_action, 'nonce' );

		$type   = $entity_type ? $entity_type : 'product';
		$params = filter_input_array( INPUT_GET, FILTER_SANITIZE_SPECIAL_CHARS );
		$res    = $this->search_entities( $params ? $params : array() );

		$this->send_success( '', $res );
	}

	/**
	 * Check if user is pro.
	 *
	 * @return bool
	 */
	public function is_pro_user() {
		return WAWP_Connector()->is_pro();
	}

	/**
	 * Check if SSO is logged in.
	 *
	 * @return bool
	 */
	private function is_sso_logged_in() {
		return WAWP_Connector()->is_connected();
	}

	/**
	 * Check if multi-language is active.
	 *
	 * @return bool
	 */
	public function is_multi_lang_active() {
		return (bool) apply_filters( 'wawp_notif_multilang_enabled', 1 === (int) WAWP::instance()->get_wawp_setting( WAWP_Settings_Registry::MULTILANG_ENABLED, 0 ) );
	}

	/**
	 * Update settings from React UI.
	 *
	 * @param array $data Data from React.
	 * @return array
	 */
	public function update_settings_from_react( $data ) {
		if ( ! current_user_can( 'manage_options' ) ) {
			return array(
				'success' => false,
				'message' => 'UNAUTHORIZED',
			);
		}
		global $wpdb;
		$languages_table = esc_sql( $wpdb->prefix . 'wawp_notif_languages' );
		WAWP()->notification_manager->set_global_settings( $data['selected_instance_ids'] ?? '', $data['admin_selected_instance_ids'] ?? '' );
		$current_codes = array_keys( $data['configured_languages'] );
		$main_lang     = $data['main_language_code'] ?? '';
		if ( ! in_array( $main_lang, $current_codes, true ) && ! empty( $current_codes ) ) {
			$main_lang = $current_codes[0];
		}
		$db = WAWP()->db;

		$all_db_codes = $db->get_col( $wpdb->prepare( 'SELECT language_code FROM %i', $languages_table ) );
		$trans_table  = $this->wawp_db_manager->tables['flow_translations'];
		if ( is_array( $all_db_codes ) ) {
			foreach ( array_diff( $all_db_codes, $current_codes ) as $code ) {
				$db->delete_row( 'languages', array( 'language_code' => $code ) );
				$db->delete_row( 'flow_translations', array( 'language_code' => $code ) );
			}
		}

		foreach ( $data['configured_languages'] as $code => $lang ) {
			if ( empty( $code ) ) {
				continue;
			}
			$db->upsert_row(
				'languages',
				array(
					'language_code' => $code,
					'name'          => $lang['name'],
					'is_main'       => ( $code === $main_lang ) ? 1 : 0,
				)
			);
		}
		wp_cache_delete( 'wawp_notif_languages', 'wawp_notif' );
		wp_cache_delete( 'wawp_notification_settings_all_v2', 'wawp_notif' );
		wp_cache_delete( 'wawp_flows_all', 'wawp_db' );

		$all_notifications_by_id = array();
		foreach ( $data['configured_languages'] as $lang_code => $lang_data ) {
			if ( empty( $lang_data['notifications'] ) || ! is_array( $lang_data['notifications'] ) ) {
				continue;
			}
			foreach ( $lang_data['notifications'] as $notif ) {
				$notif_id = $notif['id'];
				if ( ! isset( $all_notifications_by_id[ $notif_id ] ) ) {
					$all_notifications_by_id[ $notif_id ] = array(
						'flow_data'    => array(
							'name'        => $notif['name'] ?? ( is_numeric( $notif_id ) ? '' : $notif_id ),
							'trigger_key' => $notif['trigger_key'],
							'status'      => ( 'active' === $notif['enabled'] || true === $notif['enabled'] || 1 === (int) $notif['enabled'] ) ? 'active' : 'inactive',
						),
						'meta_data'    => array(
							'sender_type'                 => $notif['sender_type'],
							'whatsapp_enabled'            => $notif['whatsapp_enabled'],
							'email_enabled'               => $notif['email_enabled'],
							'admin_user_ids'              => $notif['admin_user_ids'],
							'admin_whatsapp_enabled'      => $notif['admin_whatsapp_enabled'],
							'admin_email_enabled'         => $notif['admin_email_enabled'],
							'meta_message_type'           => $notif['meta_message_type'],
							'meta_template_name'          => $notif['meta_template_name'] ?? '',
							'meta_template_lang'          => $notif['meta_template_lang'] ?? '',
							'country_filter_enabled'      => $notif['country_filter_enabled'],
							'product_filter_enabled'      => $notif['product_filter_enabled'],
							'category_filter_enabled'     => $notif['category_filter_enabled'],
							'payment_filter_enabled'      => $notif['payment_filter_enabled'],
							'billing_countries_whitelist' => $notif['billing_countries_whitelist'] ?? '',
							'billing_countries_blocklist' => $notif['billing_countries_blocklist'] ?? '',
							'payment_gateways'            => $notif['payment_gateways'] ?? '',
							'product_ids_whitelist'       => $notif['product_ids_whitelist'] ?? '',
							'product_ids_blocklist'       => $notif['product_ids_blocklist'] ?? '',
							'category_ids_whitelist'      => $notif['category_ids_whitelist'] ?? '',
							'category_ids_blocklist'      => $notif['category_ids_blocklist'] ?? '',
							'image_source_type'           => $notif['image_source_type'] ?? 'none',
							'custom_image_url'            => $notif['custom_image_url'] ?? '',
							'send_product_image'          => $notif['send_product_image'],
							'send_timing'                 => $notif['send_timing'],
							'delay_value'                 => $notif['delay_value'] ?? 0,
							'delay_unit'                  => $notif['delay_unit'] ?? 'minutes',
							'email_template_id'           => $notif['email_template_id'] ?? '',
							'admin_email_template_id'     => $notif['admin_email_template_id'] ?? '',
						),
						'translations' => array(),
					);
				}

				$all_notifications_by_id[ $notif_id ]['translations'][ $lang_code ] = array(
					'whatsapp_message'         => $notif['whatsapp_message'],
					'whatsapp_media_url'       => $notif['whatsapp_media_url'],
					'email_subject'            => $notif['email_subject'],
					'email_body'               => $notif['email_body'],
					'admin_whatsapp_message'   => $notif['admin_whatsapp_message'],
					'admin_whatsapp_media_url' => $notif['admin_whatsapp_media_url'],
					'admin_email_subject'      => $notif['admin_email_subject'],
					'admin_email_body'         => $notif['admin_email_body'],
				);
			}
		}

		$flows_table = $this->wawp_db_manager->tables['flows'];
		$steps_table = $this->wawp_db_manager->tables['flow_steps'];

		$processed_fids = array();
		foreach ( $all_notifications_by_id as $notif_id => $notif_group ) {
			$fid = is_numeric( $notif_id ) ? (int) $notif_id : $db->get_var( $wpdb->prepare( 'SELECT id FROM %i WHERE name = %s', $flows_table, $notif_id ) );

			// If searching by name failed or it was numeric, verify it exists.
			if ( $fid && ! $db->get_var( $wpdb->prepare( 'SELECT id FROM %i WHERE id = %d', $flows_table, $fid ) ) ) {
				$fid = null;
			}

			if ( $fid ) {
				// Don't update name if it was empty in the input (meaning we want to keep existing name for numeric IDs).
				if ( empty( $notif_group['flow_data']['name'] ) ) {
					unset( $notif_group['flow_data']['name'] );
				}
				$db->update_row( 'flows', $notif_group['flow_data'], array( 'id' => $fid ) );
			} else {
				// For new rules, ensure name is not empty.
				if ( empty( $notif_group['flow_data']['name'] ) ) {
					$notif_group['flow_data']['name'] = 'rule_' . time() . '_' . wp_generate_password( 4, false );
				}
				$fid = $db->insert( 'flows', $notif_group['flow_data'] );
			}

			if ( $fid ) {
				$processed_fids[] = $fid;
			}

			$sid = $db->get_var( $wpdb->prepare( "SELECT id FROM %i WHERE flow_id = %d AND step_type = 'notification' LIMIT 1", $steps_table, $fid ) );
			if ( ! $sid ) {
				$sid = $db->insert(
					'flow_steps',
					array(
						'flow_id'   => $fid,
						'step_type' => 'notification',
						'priority'  => 1,
					)
				);
			}

			$db->delete_row( 'flow_step_meta', array( 'step_id' => $sid ) );
			foreach ( $notif_group['meta_data'] as $mkey => $mval ) {
				$db->insert(
					'flow_step_meta',
					array(
						'step_id'      => $sid,
						'config_key'   => $mkey,
						'config_value' => $this->prepare_for_db( $mval ),
					)
				);
			}

			$db->delete_row( 'flow_translations', array( 'step_id' => $sid ) );
			// Clear step, meta and trans caches.
			wp_cache_delete( 'wawp_flow_step_' . $fid, 'wawp_db' );
			wp_cache_delete( 'wawp_flow_meta_' . $sid, 'wawp_db' );
			foreach ( $notif_group['translations'] as $lang_code => $tdata ) {
				wp_cache_delete( 'wawp_flow_trans_' . $sid . '_' . $lang_code, 'wawp_db' );
				if ( empty( $lang_code ) || ! is_array( $tdata ) ) {
					continue;
				}
				foreach ( $tdata as $tkey => $tval ) {
					if ( empty( $tkey ) ) {
						continue;
					}
					$db->insert(
						'flow_translations',
						array(
							'step_id'           => $sid,
							'language_code'     => $lang_code,
							'config_key'        => $tkey,
							'translation_value' => $this->prepare_for_db( $tval ),
						)
					);
				}
			}
		}

		// Delete rules that were NOT processed (meaning they were removed in the UI).
		$all_system_fids = $db->get_col( $wpdb->prepare( 'SELECT id FROM %i WHERE is_system = 0', $flows_table ) );
		foreach ( array_diff( $all_system_fids ?? array(), $processed_fids ) as $fid_to_delete ) {
			$fname = $db->get_var( $wpdb->prepare( 'SELECT name FROM %i WHERE id = %d', $flows_table, $fid_to_delete ) );
			foreach ( $db->get_col( $wpdb->prepare( 'SELECT id FROM %i WHERE flow_id = %d', $steps_table, $fid_to_delete ) ) as $sid ) {
				$db->delete_row( 'flow_step_meta', array( 'step_id' => $sid ) );
				$db->delete_row( 'flow_translations', array( 'step_id' => $sid ) );
			}
			$db->delete_row( 'flow_steps', array( 'flow_id' => $fid_to_delete ) );
			$db->delete_row( 'flows', array( 'id' => $fid_to_delete ) );
			if ( $fname ) {
				$this->clear_crons_for_rule( $fname );
			}
		}

		return array(
			'success' => true,
			'message' => 'Rules updated successfully',
		);
	}

	/**
	 * Send test notification.
	 *
	 * @param array $params Test parameters.
	 * @return array|WP_Error
	 */
	public function send_test_notification( $params ) {
		$test_user_id = isset( $params['test_admin_id'] ) ? absint( $params['test_admin_id'] ) : 0;
		$rule_raw     = $params['rule_data'] ?? null;
		$rule         = is_array( $rule_raw ) ? $rule_raw : json_decode( stripslashes( $rule_raw ), true );

		if ( ! $test_user_id || ! $rule ) {
			return new WP_Error( 'missing_params', esc_html__( 'Missing parameters for real-fire test.', 'automation-web-platform' ) );
		}

		$user = get_userdata( $test_user_id );
		if ( ! $user ) {
			return new WP_Error( 'not_found', esc_html__( 'Test recipient not found.', 'automation-web-platform' ) );
		}

		$name  = $user->display_name;
		$email = $user->user_email;
		$phone = WAWP()->user_manager->get_user_phone( $user->ID );

		if ( empty( $phone ) && ( str_contains( $rule['sender_type'], 'whatsapp' ) || str_contains( $rule['sender_type'], 'meta' ) ) ) {
			return new WP_Error( 'no_phone', esc_html__( 'User has no phone number for WhatsApp test.', 'automation-web-platform' ) );
		}

		$replacements        = array(
			'{{sitename}}'        => get_bloginfo( 'name' ),
			'{{wp-display-name}}' => $name,
			'{{order_id}}'        => 'TEST-001',
			'{{order_total}}'     => '99.00 USD',
			'{{status}}'          => __( 'Processing (Test)', 'automation-web-platform' ),
		);
		$recipient           = array(
			'user_id'      => $user->ID,
			'email'        => $email,
			'phone'        => $phone,
			'display_name' => $name,
		);
		$rule['send_timing'] = 'instant';
		$this->schedule_or_send( $rule, $replacements, $recipient, 'Manual Test Call', 'TEST-ID', $rule['trigger_key'] );

		$channels = array();
		$type     = $rule['sender_type'] ?? '';
		if ( str_contains( $type, 'whatsapp' ) || str_contains( $type, 'meta' ) ) {
			$channels[] = 'WhatsApp (' . $phone . ')';
		}
		if ( str_contains( $type, 'email' ) ) {
			$channels[] = 'Email (' . $email . ')';
		}

		$channel_str = ! empty( $channels ) ? implode( ' & ', $channels ) : __( 'selected channels', 'automation-web-platform' );

		return array(
			'success' => true,
			'data'    => array(
				/* translators: 1: name, 2: channels */
				'message' => sprintf( __( 'Simulation successful! Test results sent to %1$s via %2$s.', 'automation-web-platform' ), $name, $channel_str ),
			),
		);
	}

	/**
	 * AJAX: Send test notification.
	 */
	public function ajax_send_test_notification() {
		$this->check_permissions( 'wawp-dashboard-app', '_ajax_nonce' );
		$params = filter_input_array(
			INPUT_POST,
			array(
				'test_admin_id' => FILTER_SANITIZE_NUMBER_INT,
				'rule_data'     => FILTER_DEFAULT,
			)
		);
		$res    = $this->send_test_notification( $params );
		if ( is_wp_error( $res ) ) {
			$this->send_error( $res->get_error_message() );
		}
		$this->send_success( '', $res );
	}

	/**
	 * Get a specific rule by ID or name.
	 *
	 * @param int|string $id_or_name Rule ID or name.
	 * @param string     $lang_code  Language code.
	 * @return array|null
	 */
	private function get_rule( $id_or_name, $lang_code ) {
		global $wpdb;
		if ( ! $this->wawp_db_manager ) {
			return null;
		}
		$db          = $this->wawp_db_manager;
		$flows_table = $this->wawp_db_manager->tables['flows'];
		$steps_table = $this->wawp_db_manager->tables['flow_steps'];
		$meta_table  = $this->wawp_db_manager->tables['flow_step_meta'];
		$trans_table = $this->wawp_db_manager->tables['flow_translations'];

		$cache_key = 'wawp_flow_rule_' . md5( $id_or_name . $lang_code );
		$cached    = wp_cache_get( $cache_key, 'wawp_database' );
		if ( false !== $cached ) {
			return $cached;
		}

		$flow = is_numeric( $id_or_name ) ? $db->get_results_cached( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $this->wawp_db_manager->tables['flows'], $id_or_name ), 'wawp_flow_row_id_' . $id_or_name, 'wawp_db' ) : $db->get_results_cached( $wpdb->prepare( 'SELECT * FROM %i WHERE name = %s', $this->wawp_db_manager->tables['flows'], $id_or_name ), 'wawp_flow_row_name_' . $id_or_name, 'wawp_db' );
		$flow = ! empty( $flow ) ? $flow[0] : null;

		if ( ! $flow ) {
			return null;
		}

		$step = $db->get_results_cached( $wpdb->prepare( "SELECT * FROM %i WHERE flow_id = %d AND step_type = 'notification' LIMIT 1", $this->wawp_db_manager->tables['flow_steps'], $flow['id'] ), 'wawp_flow_step_' . $flow['id'], 'wawp_db' );
		$step = ! empty( $step ) ? $step[0] : null;

		if ( ! $step ) {
			return null;
		}

		$sid       = $step['id'];
		$meta_rows = $db->get_results_cached( $wpdb->prepare( 'SELECT config_key, config_value FROM %i WHERE step_id = %d', $this->wawp_db_manager->tables['flow_step_meta'], $sid ), 'wawp_flow_meta_' . $sid, 'wawp_db' );
		$meta      = array();
		foreach ( $meta_rows as $mr ) {
			$meta[ $mr['config_key'] ] = $this->from_db( $mr['config_value'] );
		}

		$trans_rows = $db->get_results_cached( $wpdb->prepare( 'SELECT config_key, translation_value FROM %i WHERE step_id = %d AND language_code = %s', $this->wawp_db_manager->tables['flow_translations'], $sid, $lang_code ), 'wawp_flow_trans_' . $sid . '_' . $lang_code, 'wawp_db' );

		// If no translations found for the requested language, fall back to the main language.
		if ( empty( $trans_rows ) && $this->is_multi_lang_active() ) {
			$settings  = $this->get_settings();
			$main_lang = $settings['main_language_code'] ?? 'en_US';
			if ( $main_lang !== $lang_code ) {
				$trans_rows = $db->get_results_cached( $wpdb->prepare( 'SELECT config_key, translation_value FROM %i WHERE step_id = %d AND language_code = %s', $this->wawp_db_manager->tables['flow_translations'], $sid, $main_lang ), 'wawp_flow_trans_' . $sid . '_' . $main_lang, 'wawp_db' );
				if ( ! empty( $trans_rows ) ) {
					$lang_code = $main_lang;
				}
			}
		}

		$trans = array();
		if ( $trans_rows ) {
			foreach ( $trans_rows as $tr ) {
				$trans[ $tr['config_key'] ] = $this->from_db( $tr['translation_value'] );
			}
		}

		return array_merge(
			$meta,
			$trans,
			array(
				'id'            => $flow['name'],
				'flow_id'       => $flow['id'],
				'enabled'       => ( 'active' === $flow['status'] ) ? 1 : 0,
				'language_code' => $lang_code,
				'trigger_key'   => $flow['trigger_key'],
			)
		);
	}

	/**
	 * Prepare object context for filtering.
	 *
	 * @param object $data_object Object (Order/Subscription).
	 * @return array
	 */
	public function prepare_object_context( $data_object ) {
		$pids = array();
		$cids = array();
		if ( $data_object instanceof WC_Abstract_Order ) {
			foreach ( $data_object->get_items() as $item ) {
				$pid = (int) $item->get_product_id();
				if ( $pid ) {
					$pids[] = $pid;
					$terms  = get_the_terms( $pid, 'product_cat' );
					if ( $terms && ! is_wp_error( $terms ) ) {
						foreach ( $terms as $term ) {
							$cids[] = (int) $term->term_id;
						}
					}
				}
			}

			return array(
				'product_ids'    => array_unique( $pids ),
				'category_ids'   => array_unique( $cids ),
				'country'        => $data_object->get_billing_country(),
				'payment_method' => $data_object->get_payment_method(),
			);
		}

		return array(
			'product_ids'    => array(),
			'category_ids'   => array(),
			'country'        => '',
			'payment_method' => '',
		);
	}

	/**
	 * Evaluate rule filters against context.
	 *
	 * @param array $rule    Notification rule.
	 * @param array $context Object context.
	 * @return bool
	 */
	/**
	 * Normalize a rule filter setting to an array of values.
	 *
	 * @param mixed $value     Filter setting value (array or string).
	 * @param bool  $is_numeric Whether to cast elements to integers.
	 * @return array
	 */
	private function normalize_filter_array( $value, $is_numeric = false ) {
		if ( empty( $value ) ) {
			return array();
		}
		if ( is_array( $value ) ) {
			$arr = $value;
		} else {
			$str = (string) $value;
			// Handle JSON-encoded arrays stored in the database (e.g. ["EG"] or [1,2]).
			if ( str_starts_with( trim( $str ), '[' ) ) {
				$decoded = json_decode( $str, true );
				$arr     = is_array( $decoded ) ? $decoded : array();
			} else {
				$arr = explode( ',', $str );
			}
		}
		$arr = array_filter( array_map( 'trim', $arr ) );
		if ( $is_numeric ) {
			$arr = array_filter( array_map( 'intval', $arr ) );
		}
		return array_values( $arr );
	}

	/**
	 * Evaluate rule filters against context.
	 *
	 * @param array $rule    Notification rule.
	 * @param array $context Object context.
	 * @return bool
	 */
	public function evaluate_rule_filters( $rule, $context ) {
		if ( ! empty( $rule['country_filter_enabled'] ) && ! empty( $context['country'] ) ) {
			$allow = $this->normalize_filter_array( $rule['billing_countries_whitelist'] ?? '' );
			$block = $this->normalize_filter_array( $rule['billing_countries_blocklist'] ?? '' );
			if ( ( $allow && ! in_array( $context['country'], $allow, true ) ) || ( $block && in_array( $context['country'], $block, true ) ) ) {
				return false;
			}
		}

		if ( ! empty( $rule['product_filter_enabled'] ) && ! empty( $context['product_ids'] ) ) {
			$context_pids = is_array( $context['product_ids'] ) ? $context['product_ids'] : array( $context['product_ids'] );
			$allow_p      = $this->normalize_filter_array( $rule['product_ids_whitelist'] ?? '', true );
			$block_p      = $this->normalize_filter_array( $rule['product_ids_blocklist'] ?? '', true );
			if ( $allow_p && ! array_intersect( $context_pids, $allow_p ) ) {
				return false;
			}
			if ( $block_p && array_intersect( $context_pids, $block_p ) ) {
				return false;
			}
		}

		if ( ! empty( $rule['category_filter_enabled'] ) && ! empty( $context['category_ids'] ) ) {
			$context_cids = is_array( $context['category_ids'] ) ? $context['category_ids'] : array( $context['category_ids'] );
			$allow_c      = $this->normalize_filter_array( $rule['category_ids_whitelist'] ?? '', true );
			$block_c      = $this->normalize_filter_array( $rule['category_ids_blocklist'] ?? '', true );
			if ( $allow_c && ! array_intersect( $context_cids, $allow_c ) ) {
				return false;
			}
			if ( $block_c && array_intersect( $context_cids, $block_c ) ) {
				return false;
			}
		}

		if ( ! empty( $rule['payment_filter_enabled'] ) && ! empty( $context['payment_method'] ) ) {
			$gateways = $this->normalize_filter_array( $rule['payment_gateways'] ?? '' );
			if ( $gateways && ! in_array( $context['payment_method'], $gateways, true ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Apply image overrides to rule based on context.
	 *
	 * @param array $rule    Notification rule (passed by reference).
	 * @param array $context Object context.
	 */
	public function apply_image_overrides( &$rule, $context ) {
		$img_source = isset( $rule['image_source_type'] ) ? $rule['image_source_type'] : 'none';
		if ( is_array( $img_source ) ) {
			$img_source = 'none';
		}
		if ( 'none' === $img_source && ! empty( $rule['send_product_image'] ) ) {
			$img_source = 'product';
		}
		// Ensure media URL fields are strings before we potentially overwrite them.
		if ( isset( $rule['whatsapp_media_url'] ) && is_array( $rule['whatsapp_media_url'] ) ) {
			$rule['whatsapp_media_url'] = '';
		}
		if ( isset( $rule['admin_whatsapp_media_url'] ) && is_array( $rule['admin_whatsapp_media_url'] ) ) {
			$rule['admin_whatsapp_media_url'] = '';
		}
		if ( isset( $rule['custom_image_url'] ) && is_array( $rule['custom_image_url'] ) ) {
			$rule['custom_image_url'] = '';
		}

		if ( in_array( $img_source, array( 'product', 'product_image', 'featured' ), true ) ) {
			$image_id = 0;
			if ( ! empty( $context['product_ids'] ) ) {
				$image_id = get_post_thumbnail_id( $context['product_ids'][0] );
			} elseif ( ! empty( $context['post_id'] ) ) {
				$image_id = get_post_thumbnail_id( $context['post_id'] );
			}

			if ( $image_id ) {
				$url = wp_get_attachment_url( $image_id );
				if ( $url ) {
					$rule['whatsapp_media_url']       = $url;
					$rule['admin_whatsapp_media_url'] = $url;
				}
			}
		} elseif ( 'custom' === $img_source && ! empty( $rule['custom_image_url'] ) ) {
			$rule['whatsapp_media_url']       = $rule['custom_image_url'];
			$rule['admin_whatsapp_media_url'] = $rule['custom_image_url'];
		}
	}
}
