<?php
/**
 * WordPress admin UI.
 *
 * @package RestaurantCosting
 */

declare(strict_types=1);

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

/**
 * Admin menus and actions.
 */
final class RC_Admin {

	private static ?self $instance = null;

	public static function instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	public function init(): void {
		add_action( 'admin_menu', array( $this, 'register_menus' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
		add_action( 'admin_head', array( $this, 'hide_secret_menus' ) );
		add_action( 'admin_head', array( $this, 'admin_logo' ) );
		add_action( 'admin_menu', array( $this, 'restrict_admin_menus' ), 20 );
		add_action( 'admin_init', array( $this, 'prevent_role_self_escalation' ) );

		add_action( 'admin_footer', array( $this, 'render_hal_footer' ) );

		RC_Notice::register();

		if ( RC_License::is_demo() ) {
			add_action( 'admin_notices', array( $this, 'render_demo_banner' ) );
			add_action( 'admin_head', array( $this, 'render_demo_banner_css' ) );
		}

		add_action( 'admin_post_rc_import_foodmaster', array( $this, 'handle_import' ) );
		add_action( 'admin_post_rc_upload_foodmaster', array( $this, 'handle_upload_foodmaster' ) );
		add_action( 'admin_post_rc_export_ingredients', array( $this, 'handle_export' ) );
		add_action( 'admin_post_rc_backup_database', array( $this, 'handle_backup' ) );
		add_action( 'admin_post_rc_restore_database', array( $this, 'handle_restore' ) );
		add_action( 'admin_post_rc_delete_all_data', array( $this, 'handle_delete_all_data' ) );
		add_action( 'admin_post_rc_download_template', array( $this, 'handle_download_template' ) );
		add_action( 'admin_post_rc_save_recipe', array( $this, 'handle_save_recipe' ) );
		add_action( 'admin_post_rc_delete_recipe', array( $this, 'handle_delete_recipe' ) );
		add_action( 'admin_post_rc_print_recipe', array( $this, 'handle_print_recipe' ) );
		add_action( 'admin_post_rc_copy_recipe', array( $this, 'handle_copy_recipe' ) );
		add_action( 'admin_post_rc_toggle_recipe_status', array( $this, 'handle_toggle_recipe_status' ) );
		add_action( 'admin_post_rc_save_supplier', array( $this, 'handle_save_supplier' ) );
		add_action( 'admin_post_rc_delete_supplier', array( $this, 'handle_delete_supplier' ) );
		add_action( 'admin_post_rc_export_suppliers', array( $this, 'handle_export_suppliers' ) );
		add_action( 'admin_post_rc_save_inventory', array( $this, 'handle_save_inventory' ) );
		add_action( 'wp_ajax_rc_save_inventory_ajax', array( $this, 'ajax_save_inventory' ) );
		add_action( 'admin_post_rc_delete_inventory', array( $this, 'handle_delete_inventory' ) );
		add_action( 'admin_post_rc_print_inventory', array( $this, 'handle_print_inventory' ) );
		add_action( 'wp_ajax_rc_send_order_email', array( $this, 'ajax_send_order_email' ) );
		add_action( 'admin_post_rc_save_area', array( $this, 'handle_save_area' ) );
		add_action( 'admin_post_rc_delete_area', array( $this, 'handle_delete_area' ) );
		add_action( 'admin_post_rc_move_area', array( $this, 'handle_move_area' ) );
		add_action( 'admin_post_rc_save_display_settings', array( $this, 'handle_save_display_settings' ) );
		add_action( 'wp_ajax_rc_save_display_settings', array( $this, 'ajax_save_display_settings' ) );

		add_action( 'wp_ajax_rc_search_ingredients', array( $this, 'ajax_search_ingredients' ) );
		add_action( 'wp_ajax_rc_calculate_recipe', array( $this, 'ajax_calculate_recipe' ) );
		add_action( 'wp_ajax_rc_save_ingredient', array( $this, 'ajax_save_ingredient' ) );
		add_action( 'wp_ajax_rc_delete_ingredient', array( $this, 'ajax_delete_ingredient' ) );
		add_action( 'wp_ajax_rc_get_ingredient', array( $this, 'ajax_get_ingredient' ) );
		add_action( 'wp_ajax_rc_check_recipe_name', array( $this, 'ajax_check_recipe_name' ) );
		add_action( 'wp_ajax_rc_add_food_type', array( $this, 'ajax_add_food_type' ) );
		add_action( 'wp_ajax_rc_get_supplier', array( $this, 'ajax_get_supplier' ) );
		add_action( 'wp_ajax_rc_save_supplier', array( $this, 'ajax_save_supplier' ) );
		add_action( 'wp_ajax_rc_add_supplier', array( $this, 'ajax_add_supplier' ) );
		add_action( 'wp_ajax_rc_add_area', array( $this, 'ajax_add_area' ) );
		add_action( 'wp_ajax_rc_add_menu_type', array( $this, 'ajax_add_menu_type' ) );
		add_action( 'wp_ajax_rc_delete_menu_type', array( $this, 'ajax_delete_menu_type' ) );
		add_action( 'wp_ajax_rc_save_restaurant_info', array( $this, 'ajax_save_restaurant_info' ) );
		add_action( 'wp_ajax_rc_get_restaurant_info', array( $this, 'ajax_get_restaurant_info' ) );
		add_action( 'wp_ajax_rc_upload_restaurant_logo', array( $this, 'ajax_upload_restaurant_logo' ) );
		add_action( 'wp_ajax_rc_get_inventory_data', array( $this, 'ajax_get_inventory_data' ) );
		add_action( 'wp_ajax_rc_toggle_print_image', array( $this, 'ajax_toggle_print_image' ) );
		add_action( 'admin_post_rc_delete_user', array( $this, 'handle_delete_user' ) );
		add_action( 'wp_ajax_rc_get_user', array( $this, 'ajax_get_user' ) );
		add_action( 'wp_ajax_rc_save_user', array( $this, 'ajax_save_user' ) );
		add_action( 'wp_ajax_rc_reset_password', array( $this, 'ajax_reset_password' ) );

		add_action( 'admin_post_rc_save_license', array( $this, 'handle_save_license' ) );

		add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widget' ) );

		add_filter( 'admin_body_class', array( $this, 'admin_body_theme_class' ) );
		add_action( 'wp_ajax_rc_save_theme', array( $this, 'ajax_save_theme' ) );
	}

	/**
	 * Save license key from Settings page.
	 */
	public function handle_save_license(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing-settings' ) );
			exit;
		}

		check_admin_referer( 'rc_save_license' );

		$key = isset( $_POST['rc_license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['rc_license_key'] ) ) : '';
		RC_License_Client::set_license_key( $key );

		// Force a verification so the user sees an immediate result.
		$result = RC_License_Client::verify( true );
		if ( $result['valid'] ) {
			RC_Notice::success( __( 'License verified and saved.', 'restaurant-costing' ) );
		} else {
			RC_Notice::error( sprintf( __( 'License check failed: %s (status: %s)', 'restaurant-costing' ), $result['message'], $result['status'] ) );
		}

		wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing-settings' ) );
		exit;
	}

	public function render_demo_banner(): void {
		$screen = get_current_screen();
		if ( $screen && ! str_contains( $screen->id, 'restaurant-costing' ) ) {
			return;
		}
		$over = RC_License::over_cap_counts();
		$warnings = array();
		if ( isset( $over['ingredients'] ) ) {
			$warnings[] = sprintf( __( '%d ingredient(s) over the demo limit (50). Upgrade to keep access.', 'restaurant-costing' ), $over['ingredients'] );
		}
		if ( isset( $over['recipes'] ) ) {
			$warnings[] = sprintf( __( '%d recipe(s) over the demo limit (10). Upgrade to keep access.', 'restaurant-costing' ), $over['recipes'] );
		}
		echo '<div id="rc-demo-banner">DEMO VERSION';
		if ( ! empty( $warnings ) ) {
			echo ' &mdash; ' . esc_html( implode( ' ', $warnings ) );
		}
		echo '</div>';
	}

	public function admin_logo(): void {
		$screen = get_current_screen();
		if ( ! $screen || ! str_contains( $screen->id, 'restaurant-costing' ) ) {
			return;
		}
		$logo_url = RC_PLUGIN_URL . 'admin/assets/salamander-logo.png';
		?>
		<style>
		.rc-page-header {
			display: flex;
			align-items: center;
			gap: 16px;
			padding-bottom: 15px;
			margin-bottom: 16px;
			border-bottom: 2px solid rgba(255,255,255,0.15);
		}
		body.rc-theme-light .rc-page-header {
			border-bottom: 2px solid rgba(0,0,0,0.12);
		}
		.rc-page-header .rc-logo {
			width: 80px;
			height: 80px;
			flex-shrink: 0;
			background: url(<?php echo esc_url( $logo_url ); ?>) no-repeat center center;
			background-size: contain;
		}
		.rc-page-header .rc-header-text {
			flex: 1;
		}
		.rc-page-header h1:first-of-type {
			padding: 0;
			margin: 0;
			min-height: auto;
			line-height: 1.3;
		}
		.rc-page-header .rc-version {
			font-size: 0.9em;
			font-style: italic;
			color: #8c8f94;
			margin-bottom: 0;
		}
		</style>
		<script>
		jQuery(function($) {
			var wrap = $('.wrap').first();
			if (wrap.find('.rc-page-header').length) return;
			var h1 = wrap.find('h1').first();
			var ver = wrap.find('.rc-version').first();
			if (!h1.length) return;
			var header = $('<div class="rc-page-header">').prependTo(wrap);
			$('<div class="rc-logo">').appendTo(header);
			var textCol = $('<div class="rc-header-text">').appendTo(header);
			h1.detach().appendTo(textCol);
			if (ver.length) ver.detach().appendTo(textCol);
		});
		</script>
		<?php
	}

	public function render_demo_banner_css(): void {
		?>
		<style>
		#rc-demo-banner {
			background: #d4edf0;
			color: #0c5460;
			text-align: center;
			padding: 8px 0;
			font-size: 16px;
			font-weight: 700;
			letter-spacing: 1px;
			width: 100%;
			margin-left: -20px;
			padding-left: 20px;
			padding-right: 20px;
			box-sizing: border-box;
			border-bottom: 1px solid #b8e0e5;
		}
		#wpcontent { padding-top: 0; }
		</style>
		<?php
	}

	public function register_menus(): void {
		add_menu_page(
			__( 'Restaurant Costing', 'restaurant-costing' ),
			__( 'Restaurant Costing', 'restaurant-costing' ),
			'read',
			'restaurant-costing',
			array( $this, 'render_dashboard' ),
			RC_PLUGIN_URL . 'admin/assets/sal_icon.png',
			56
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Ingredients', 'restaurant-costing' ),
			__( 'Ingredients', 'restaurant-costing' ),
			RC_Roles::CAP_VIEW_INGREDIENTS,
			'restaurant-costing-ingredients',
			array( $this, 'render_ingredients' )
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Recipes', 'restaurant-costing' ),
			__( 'Recipes', 'restaurant-costing' ),
			RC_Roles::CAP_VIEW_RECIPES,
			'restaurant-costing-recipes',
			array( $this, 'render_recipes' )
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Suppliers', 'restaurant-costing' ),
			__( 'Suppliers', 'restaurant-costing' ),
			RC_Roles::CAP_MANAGE_SUPPLIERS,
			'restaurant-costing-suppliers',
			array( $this, 'render_suppliers' )
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Edit Recipe', 'restaurant-costing' ),
			__( 'Edit Recipe', 'restaurant-costing' ),
			RC_Roles::CAP_EDIT_RECIPES,
			'restaurant-costing-recipe-edit',
			array( $this, 'render_recipe_edit' )
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Print Recipe', 'restaurant-costing' ),
			__( 'Print Recipe', 'restaurant-costing' ),
			RC_Roles::CAP_PRINT,
			'restaurant-costing-recipe-print',
			array( $this, 'render_recipe_print' )
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Inventory', 'restaurant-costing' ),
			__( 'Inventory', 'restaurant-costing' ),
			RC_Roles::CAP_ACCESS_INVENTORY,
			'restaurant-costing-inventory',
			array( $this, 'render_inventory_ae' )
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Areas', 'restaurant-costing' ),
			__( 'Areas', 'restaurant-costing' ),
			RC_Roles::CAP_MANAGE_AREAS,
			'restaurant-costing-areas',
			array( $this, 'render_areas' )
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Inventory Entry', 'restaurant-costing' ),
			__( 'Inventory Entry', 'restaurant-costing' ),
			RC_Roles::CAP_ACCESS_INVENTORY,
			'restaurant-costing-inventory-we',
			array( $this, 'render_inventory_we' )
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Settings', 'restaurant-costing' ),
			__( 'Settings', 'restaurant-costing' ),
			RC_Roles::CAP_MANAGE_SETTINGS,
			'restaurant-costing-settings',
			array( $this, 'render_settings' )
		);

		add_submenu_page(
			'restaurant-costing',
			__( 'Users', 'restaurant-costing' ),
			__( 'Users', 'restaurant-costing' ),
			RC_Roles::CAP_MANAGE_USERS,
			'restaurant-costing-users',
			array( $this, 'render_users' )
		);
	}

	/**
	 * Injects CSS into the admin head to hide secret submenu items and
	 * non-RC admin bar items for RC roles.
	 */
	public function hide_secret_menus(): void {
		$user = wp_get_current_user();
		$rc_roles = array( RC_Roles::ROLE_USER, RC_Roles::ROLE_MANAGER );
		$has_rc_role = $user && ! empty( array_intersect( $rc_roles, (array) $user->roles ) );
		?>
		<style>
			.rc-version { font-size:1em; font-style:italic; margin-bottom:8px; }
			#adminmenu .wp-submenu li:has(a[href*="restaurant-costing-recipe-edit"]),
			#adminmenu .wp-submenu li:has(a[href*="restaurant-costing-recipe-print"]),
			#adminmenu .wp-submenu li:has(a[href*="restaurant-costing-inventory-we"]),
			#adminmenu .wp-submenu li:has(a[href*="restaurant-costing-inventory-print"]),
			#adminmenu .wp-submenu li:has(a[href*="restaurant-costing-users"]),
			#adminmenu .wp-submenu li:has(a[href*="restaurant-costing-areas"]),
			#adminmenu .wp-submenu li:has(a[href*="restaurant-costing-suppliers"]) {
				display: none !important;
			}
			<?php if ( $has_rc_role ) : ?>
			#wp-admin-bar-new-content,
			#wp-admin-bar-comments,
			#wp-admin-bar-updates {
				display: none !important;
			}
			<?php endif; ?>
		</style>
		<?php
	}

	/**
	 * Removes non-RC admin menu items for RC User and RC Manager roles.
	 */
	public function restrict_admin_menus(): void {
		if ( ! is_user_logged_in() ) {
			return;
		}

		$user = wp_get_current_user();
		if ( ! $user ) {
			return;
		}

		$rc_roles = array( RC_Roles::ROLE_USER, RC_Roles::ROLE_MANAGER );
		if ( empty( array_intersect( $rc_roles, (array) $user->roles ) ) ) {
			return;
		}

		global $menu, $submenu;
		$allowed = array( 'restaurant-costing', 'index.php', 'users.php', 'profile.php' );

		foreach ( $menu as $key => $item ) {
			if ( empty( $item[2] ) ) {
				continue;
			}
			$found = false;
			foreach ( $allowed as $slug ) {
				if ( str_contains( $item[2], $slug ) ) {
					$found = true;
					break;
				}
			}
			if ( ! $found ) {
				remove_menu_page( $item[2] );
			}
		}

		foreach ( $submenu as $parent => $items ) {
			if ( str_contains( $parent, 'restaurant-costing' ) ) {
				foreach ( $items as $key => $item ) {
					if ( ! current_user_can( $item[1] ?? 'read' ) ) {
						unset( $submenu[ $parent ][ $key ] );
					}
				}
			}
			if ( str_contains( $parent, 'users.php' ) ) {
				foreach ( $items as $key => $item ) {
					if ( ! str_contains( $item[2] ?? '', 'profile' ) ) {
						unset( $submenu[ $parent ][ $key ] );
					}
				}
			}
		}
	}

	/**
	 * Prevents RC User and RC Manager from changing their own role in profile.
	 */
	public function prevent_role_self_escalation(): void {
		if ( ! is_user_logged_in() ) {
			return;
		}

		$user = wp_get_current_user();
		if ( ! $user ) {
			return;
		}

		$rc_roles = array( RC_Roles::ROLE_USER, RC_Roles::ROLE_MANAGER );
		if ( empty( array_intersect( $rc_roles, (array) $user->roles ) ) ) {
			return;
		}

		add_action( 'admin_head-user-edit.php', array( $this, 'disable_role_dropdown' ) );
		add_action( 'admin_head-profile.php', array( $this, 'disable_role_dropdown' ) );
	}

	public function disable_role_dropdown(): void {
		?>
		<style>
			#role { display: none; }
		</style>
		<script>
		(function($) {
			$(function() {
				if ($('#role').length) {
					$('#role').prop('disabled', true).after('<p><em><?php echo esc_js( __( 'Role cannot be changed via profile. Contact an administrator.', 'restaurant-costing' ) ); ?></em></p>');
				}
			});
		})(jQuery);
		</script>
		<?php
	}

	public function enqueue_assets( string $hook ): void {
		$on_rc_page     = str_contains( $hook, 'restaurant-costing' );
		$on_recipe      = str_contains( $hook, 'restaurant-costing-recipe-edit' );
		$on_ingredients = str_contains( $hook, 'restaurant-costing-ingredients' );
		$on_settings    = str_contains( $hook, 'restaurant-costing-settings' );
		$on_inventory   = str_contains( $hook, 'restaurant-costing-inventory' );
		$on_areas       = str_contains( $hook, 'restaurant-costing-areas' );
		$css_base       = RC_PLUGIN_DIR . 'admin/assets/';

		if ( $on_rc_page ) {
			$util_ver = file_exists( $css_base . 'admin-utility.css' ) ? filemtime( $css_base . 'admin-utility.css' ) : RC_VERSION;
			wp_enqueue_style(
				'rc-admin-utility',
				RC_PLUGIN_URL . 'admin/assets/admin-utility.css',
				array(),
				$util_ver
			);

			$common_ver = file_exists( $css_base . 'admin-common.css' ) ? filemtime( $css_base . 'admin-common.css' ) : RC_VERSION;
			wp_enqueue_style(
				'rc-admin-common',
				RC_PLUGIN_URL . 'admin/assets/admin-common.css',
				array(),
				$common_ver
			);

			$dark_ver = file_exists( $css_base . 'admin-dark-theme.css' ) ? filemtime( $css_base . 'admin-dark-theme.css' ) : RC_VERSION;
			wp_enqueue_style(
				'rc-admin-dark-theme',
				RC_PLUGIN_URL . 'admin/assets/admin-dark-theme.css',
				array(),
				$dark_ver
			);

			$theme_ver = file_exists( $css_base . 'admin-theme.css' ) ? filemtime( $css_base . 'admin-theme.css' ) : RC_VERSION;
			wp_enqueue_style(
				'rc-admin-theme',
				RC_PLUGIN_URL . 'admin/assets/admin-theme.css',
				array(),
				$theme_ver
			);

			wp_add_inline_script( 'jquery', 'var rcThemeData = ' . wp_json_encode( array(
				'current' => get_option( 'rc_theme', 'dark' ),
			) ) . ';' . '
			function rcSetCookie(name,val){var d=new Date();d.setTime(d.getTime()+365*24*60*60*1000);document.cookie=name+"="+encodeURIComponent(val)+"; expires="+d.toUTCString()+"; path=/";}
			function rcPageKey(){var m=window.location.href.match(/page=restaurant-costing-([a-z-]+)/);return m?m[1].replace(/-/g,"_"):"default";}
			jQuery(function($){$(document).on("change","select[name=\'per_page\']",function(){rcSetCookie("rc_pp_"+rcPageKey(),$(this).val());});});', 'before' );

			wp_enqueue_script(
				'rc-admin-confirm-dialog',
				RC_PLUGIN_URL . 'admin/assets/admin-confirm-dialog.js',
				array( 'jquery' ),
				RC_VERSION,
				true
			);
			wp_localize_script(
				'rc-admin-confirm-dialog',
				'rcConfirmL10n',
				array(
					'ok'     => __( 'Delete', 'restaurant-costing' ),
					'cancel' => __( 'Cancel', 'restaurant-costing' ),
					'okToggle' => __( 'Toggle', 'restaurant-costing' ),
				)
			);
		}

		if ( $on_settings ) {
			wp_enqueue_media();
			wp_enqueue_script(
				'rc-admin-settings',
				RC_PLUGIN_URL . 'admin/assets/admin-settings.js',
				array( 'jquery' ),
				RC_VERSION,
				true
			);
			wp_localize_script(
				'rc-admin-settings',
				'rcSettingsData',
				array(
					'ajaxUrl' => admin_url( 'admin-ajax.php' ),
					'nonce'   => wp_create_nonce( 'rc_recipe_ajax' ),
				)
			);
		}

		if ( $on_inventory || $on_areas ) {
			$inv_css_ver = file_exists( $css_base . 'admin-inventory.css' ) ? filemtime( $css_base . 'admin-inventory.css' ) : RC_VERSION;
			wp_enqueue_style(
				'rc-admin-inventory',
				RC_PLUGIN_URL . 'admin/assets/admin-inventory.css',
				array(),
				$inv_css_ver
			);
			wp_enqueue_script(
				'rc-admin-inventory',
				RC_PLUGIN_URL . 'admin/assets/admin-inventory.js',
				array( 'jquery' ),
				RC_VERSION,
				true
			);
			wp_localize_script(
				'rc-admin-inventory',
				'rcInventory',
				array(
					'ajaxUrl' => admin_url( 'admin-ajax.php' ),
					'nonce'   => wp_create_nonce( 'rc_inventory_ajax' ),
				)
			);
			return;
		}

		if ( ! $on_recipe && ! $on_ingredients ) {
			return;
		}

		$recipe_css_ver = file_exists( $css_base . 'admin-recipe.css' ) ? filemtime( $css_base . 'admin-recipe.css' ) : RC_VERSION;
		wp_enqueue_style(
			'rc-admin-recipe',
			RC_PLUGIN_URL . 'admin/assets/admin-recipe.css',
			array(),
			$recipe_css_ver
		);

		$nft_css_ver = file_exists( $css_base . 'admin-nft.css' ) ? filemtime( $css_base . 'admin-nft.css' ) : RC_VERSION;
		wp_enqueue_style(
			'rc-admin-nft',
			RC_PLUGIN_URL . 'admin/assets/admin-nft.css',
			array(),
			$nft_css_ver
		);

		if ( $on_recipe ) {
			wp_enqueue_media();
			wp_enqueue_script(
				'rc-admin-recipe',
				RC_PLUGIN_URL . 'admin/assets/admin-recipe.js',
				array( 'jquery', 'jquery-ui-sortable', 'media-editor' ),
				RC_VERSION,
				true
			);

			wp_localize_script(
				'rc-admin-recipe',
				'rcRecipe',
				array(
					'ajaxUrl' => admin_url( 'admin-ajax.php' ),
					'nonce'   => wp_create_nonce( 'rc_recipe_ajax' ),
				)
			);
		}

		$this->enqueue_ingredient_modal( $on_ingredients ? 'ingredients-list' : 'recipe-edit', $css_base );
	}

	private function get_per_page( string $page_key = 'default' ): int {
		$cookie_name = 'rc_pp_' . $page_key;
		if ( isset( $_GET['per_page'] ) ) {
			$pp = '-1' === (string) $_GET['per_page'] ? -1 : max( 1, (int) $_GET['per_page'] );
			setcookie( $cookie_name, (string) $pp, time() + 365 * 24 * 3600, '/' );
			$_COOKIE[ $cookie_name ] = (string) $pp;
			return $pp;
		}
		$cookie = isset( $_COOKIE[ $cookie_name ] ) ? (int) $_COOKIE[ $cookie_name ] : 25;
		return $cookie > 0 ? $cookie : 25;
	}

	private function enqueue_ingredient_modal( string $context, string $css_base ): void {
		$modal_js_ver = file_exists( $css_base . 'admin-ingredient-modal.js' ) ? filemtime( $css_base . 'admin-ingredient-modal.js' ) : RC_VERSION;
		wp_enqueue_script(
			'rc-admin-ingredient-modal',
			RC_PLUGIN_URL . 'admin/assets/admin-ingredient-modal.js',
			array( 'jquery' ),
			$modal_js_ver,
			true
		);

		wp_localize_script(
			'rc-admin-ingredient-modal',
			'rcIngredientModal',
			array(
				'ajaxUrl' => admin_url( 'admin-ajax.php' ),
				'nonce'   => wp_create_nonce( 'rc_recipe_ajax' ),
				'context'  => $context,
			)
		);

		$modal_css_ver = file_exists( $css_base . 'admin-ingredient-modal.css' ) ? filemtime( $css_base . 'admin-ingredient-modal.css' ) : RC_VERSION;
		wp_enqueue_style(
			'rc-admin-ingredient-modal',
			RC_PLUGIN_URL . 'admin/assets/admin-ingredient-modal.css',
			array(),
			$modal_css_ver
		);
	}

	public function render_dashboard(): void {
		if ( ! current_user_can( 'read' ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		require RC_PLUGIN_DIR . 'admin/views/dashboard.php';
	}

	public function render_ingredients(): void {
		if ( ! current_user_can( RC_Roles::CAP_VIEW_INGREDIENTS ) ) {
			RC_Notice::error( __( 'You do not have permission to view ingredients.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		$per_page = $this->get_per_page( 'ingredients' );
		$search   = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
		$f_type   = isset( $_GET['f_type'] ) ? sanitize_text_field( wp_unslash( $_GET['f_type'] ) ) : '';
		$f_sup    = isset( $_GET['f_supplier'] ) ? sanitize_text_field( wp_unslash( $_GET['f_supplier'] ) ) : '';
		$orderby  = isset( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : 'name';
		$order    = isset( $_GET['order'] ) ? sanitize_text_field( wp_unslash( $_GET['order'] ) ) : 'ASC';
		$page     = isset( $_GET['paged'] ) ? max( 1, (int) $_GET['paged'] ) : 1;
		$limit    = ( -1 === $per_page ) ? PHP_INT_MAX : $per_page;
		$offset   = ( $page - 1 ) * $limit;

		$filters = array( 's' => $search, 'f_type' => $f_type, 'f_supplier' => $f_sup, 'show_inactive' => $show_inactive );

		$items     = RC_Ingredient_Repository::list( $limit, $offset, $filters, $orderby, strtoupper( $order ) === 'DESC' ? 'DESC' : 'ASC' );
		$total     = RC_Ingredient_Repository::count( $filters );
		$total_all = RC_Ingredient_Repository::count();

		require RC_PLUGIN_DIR . 'admin/views/ingredients-list.php';
	}

	public function render_recipes(): void {
		if ( ! current_user_can( RC_Roles::CAP_VIEW_RECIPES ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		$per_page      = $this->get_per_page( 'recipes' );
		$search        = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
		$orderby       = isset( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : 'name';
		$order         = isset( $_GET['order'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) : 'ASC';
		$show_inactive       = ! empty( $_GET['show_inactive'] );
		$show_menu_items     = ! empty( $_GET['show_menu_items'] );
		$show_sub_recipes    = ! empty( $_GET['show_sub_recipes'] );

		// Default to Menu Items on fresh page load only
		$has_filter = isset( $_GET['per_page'] ) || ! empty( $_GET['s'] ) || ! empty( $_GET['menu_type'] );
		if ( ! $has_filter ) {
			$show_menu_items = true;
		}
		$menu_type_filter    = isset( $_GET['menu_type'] ) ? (int) $_GET['menu_type'] : 0;

		// Translate checkbox state to is_ingredient filter
		if ( $show_menu_items && ! $show_sub_recipes ) {
			$is_ingredient_filter = 'no';
		} elseif ( ! $show_menu_items && $show_sub_recipes ) {
			$is_ingredient_filter = 'yes';
		} elseif ( ! $show_menu_items && ! $show_sub_recipes ) {
			$is_ingredient_filter = 'none'; // force empty
		} else {
			$is_ingredient_filter = '';
		}

		$page                = isset( $_GET['paged'] ) ? max( 1, (int) $_GET['paged'] ) : 1;
		$limit               = ( -1 === $per_page ) ? PHP_INT_MAX : $per_page;
		$offset              = ( $page - 1 ) * (int) $limit;

		$items = RC_Recipe_Repository::list( $limit, $offset, $search, $orderby, $order === 'DESC' ? 'DESC' : 'ASC', $show_inactive, $is_ingredient_filter, $menu_type_filter );
		$total = RC_Recipe_Repository::count( $search, $show_inactive, $is_ingredient_filter, $menu_type_filter );

		require RC_PLUGIN_DIR . 'admin/views/recipes-list.php';
	}

	public function render_suppliers(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SUPPLIERS ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		$per_page = $this->get_per_page( 'suppliers' );
		$search   = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
		$orderby  = isset( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : 'business_name';
		$order    = isset( $_GET['order'] ) ? sanitize_text_field( wp_unslash( $_GET['order'] ) ) : 'ASC';
		$page     = isset( $_GET['paged'] ) ? max( 1, (int) $_GET['paged'] ) : 1;
		$limit    = ( -1 === $per_page ) ? PHP_INT_MAX : $per_page;
		$offset   = ( $page - 1 ) * $limit;

		$filters = array( 's' => $search );

		$items = RC_Supplier_Repository::list( $limit, $offset, $filters, $orderby, strtoupper( $order ) === 'DESC' ? 'DESC' : 'ASC' );
		$total = RC_Supplier_Repository::count( $filters );

		require RC_PLUGIN_DIR . 'admin/views/suppliers-list.php';
	}

	public function render_recipe_edit(): void {
		if ( ! current_user_can( RC_Roles::CAP_EDIT_RECIPES ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		$recipe_id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		$full      = $recipe_id > 0 ? RC_Recipe_Repository::get_full( $recipe_id ) : null;

		if ( $full ) {
			$recipe       = $full['recipe'];
			$lines        = $full['lines'];
			$labor        = $full['labor'];
			$totals       = $full['totals'];
			$is_ingredient = (int) ( $recipe->is_ingredient ?? 0 );
		} else {
			$copy_key   = isset( $_GET['rc_copy'] ) ? sanitize_key( $_GET['rc_copy'] ) : '';
			$copy_data = $copy_key ? get_transient( $copy_key ) : null;

			if ( $copy_data ) {
				delete_transient( $copy_key );
				$recipe         = (object) $copy_data;
				$recipe->name   = '';
				$recipe->id     = 0;
				$lines          = array_map( static function ( array $l ): object { return (object) $l; }, $copy_data['lines'] ?? array() );
				$labor          = array_map( static function ( array $l ): object { return (object) $l; }, $copy_data['labor'] ?? array() );
				$totals         = RC_Recipe_Calculator::calculate( $recipe, $lines, $labor );
				$is_ingredient  = 0;
			} else {
				$recipe        = null;
				$lines         = array();
				$labor         = array();
				$totals        = array();
				$is_ingredient = 0;
			}
		}

		require RC_PLUGIN_DIR . 'admin/views/recipe-edit.php';
	}

	public function render_recipe_print(): void {
		if ( ! current_user_can( RC_Roles::CAP_PRINT ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		require RC_PLUGIN_DIR . 'admin/views/recipe-print.php';
	}

	public function render_settings(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		require RC_PLUGIN_DIR . 'admin/views/settings.php';
	}

	public function render_users(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_USERS ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}

		$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';

		$args = array(
			'role__in' => array( RC_Roles::ROLE_USER, RC_Roles::ROLE_MANAGER ),
			'fields'   => 'all_with_meta',
			'orderby'  => 'login',
			'order'    => 'ASC',
		);

		if ( '' !== $search ) {
			$args['search'] = '*' . $search . '*';
			$args['search_columns'] = array( 'user_login', 'user_email', 'first_name', 'last_name' );
		}

		$users = get_users( $args );

		foreach ( $users as $user ) {
			$user->phone = get_user_meta( $user->ID, 'phone_number', true );
		}

		$items = $users;

		require RC_PLUGIN_DIR . 'admin/views/users-list.php';
	}

	public function render_inventory_ae(): void {
		if ( ! current_user_can( RC_Roles::CAP_ACCESS_INVENTORY ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		$per_page = $this->get_per_page( 'inventory' );
		$search   = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
		$orderby  = isset( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : 'created_at';
		$order    = isset( $_GET['order'] ) ? sanitize_text_field( wp_unslash( $_GET['order'] ) ) : 'DESC';
		$page     = isset( $_GET['paged'] ) ? max( 1, (int) $_GET['paged'] ) : 1;
		$limit    = ( -1 === $per_page ) ? PHP_INT_MAX : $per_page;
		$offset   = ( $page - 1 ) * $limit;

		$filters = array( 's' => $search );
		$items   = RC_Inventory_Repository::list( $limit, $offset, $filters, $orderby, strtoupper( $order ) === 'ASC' ? 'ASC' : 'DESC' );
		$total   = RC_Inventory_Repository::count( $filters );

		require RC_PLUGIN_DIR . 'admin/views/inventory-ae.php';
	}

	public function render_inventory_we(): void {
		if ( ! current_user_can( RC_Roles::CAP_ACCESS_INVENTORY ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		$inventory_id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		$inventory    = null;
		$items        = array();

		if ( $inventory_id > 0 ) {
			$inventory = RC_Inventory_Repository::get( $inventory_id );
			$items     = RC_Inventory_Repository::get_items( $inventory_id );
		}

		require RC_PLUGIN_DIR . 'admin/views/inventory-we.php';
	}

	public function render_areas(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_AREAS ) ) {
			RC_Notice::error( __( 'You do not have permission to access this page.', 'restaurant-costing' ) );
			wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing' ) );
			exit;
		}
		RC_Area_Repository::repair_sort_orders();
		$items = RC_Area_Repository::get_all();
		require RC_PLUGIN_DIR . 'admin/views/areas-list.php';
	}

	public function handle_save_inventory(): void {
		if ( ! current_user_can( RC_Roles::CAP_ACCESS_INVENTORY ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_save_inventory' );

		try {
			$id       = isset( $_POST['inventory_id'] ) ? (int) $_POST['inventory_id'] : 0;
			$existing = null;

			if ( $id > 0 ) {
				$existing = RC_Inventory_Repository::get( $id );
				$name     = $existing ? (string) ( $existing->name ?? '' ) : '';
			} else {
				$name = 'Inventory ' . current_time( 'Y-m-d' );
			}

			$raw_items  = isset( $_POST['items'] ) && is_array( $_POST['items'] ) ? wp_unslash( $_POST['items'] ) : array();
			$one_time_items = isset( $_POST['one_time_items'] ) && is_array( $_POST['one_time_items'] ) ? wp_unslash( $_POST['one_time_items'] ) : array();
			$no_supplier_items = isset( $_POST['no_supplier_items'] ) && is_array( $_POST['no_supplier_items'] ) ? wp_unslash( $_POST['no_supplier_items'] ) : array();

			$user       = wp_get_current_user();
			$is_rc_user = in_array( RC_Roles::ROLE_USER, (array) $user->roles, true );
			$items      = array();

			foreach ( $raw_items as $row ) {
				$items[] = array(
					'ingredient_name' => sanitize_text_field( (string) ( $row['ingredient_name'] ?? '' ) ),
					'supplier_name'   => $is_rc_user ? sanitize_text_field( (string) ( $existing->supplier_name ?? '' ) ) : sanitize_text_field( (string) ( $row['supplier_name'] ?? '' ) ),
					'min_level'       => $is_rc_user ? (int) ( $existing->min_level ?? 0 ) : (int) ( $row['min_level'] ?? 0 ),
					'order_qty'       => (float) ( $row['order_qty'] ?? 0 ),
				);
			}

			foreach ( $one_time_items as $row ) {
				if ( $is_rc_user ) {
					continue;
				}
				$name = sanitize_text_field( (string) ( $row['name'] ?? '' ) );
				if ( '' === $name ) {
					continue;
				}
				$items[] = array(
					'ingredient_name' => $name,
					'supplier_name'   => '',
					'min_level'       => 0,
					'order_qty'       => (float) ( $row['order_qty'] ?? 0 ),
				);
			}

			foreach ( $no_supplier_items as $row ) {
				$name = sanitize_text_field( (string) ( $row['name'] ?? '' ) );
				if ( '' === $name ) {
					continue;
				}
				$items[] = array(
					'ingredient_name' => $name,
					'supplier_name'   => '',
					'min_level'       => (int) ( $row['min_level'] ?? 0 ),
					'order_qty'       => (float) ( $row['order_qty'] ?? 0 ),
				);
			}

			if ( $id > 0 ) {
				RC_Inventory_Repository::update( $id, $name, $items );
			} else {
				$id = RC_Inventory_Repository::create( $name, $items );
			}

			RC_Notice::success( __( 'Inventory saved.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function ajax_save_inventory(): void {
		check_ajax_referer( 'rc_inventory_ajax', 'nonce' );
		if ( ! current_user_can( RC_Roles::CAP_ACCESS_INVENTORY ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}
		try {
			$id = isset( $_POST['inventory_id'] ) ? (int) $_POST['inventory_id'] : 0;
			$existing = null;
			if ( $id > 0 ) {
				$existing = RC_Inventory_Repository::get( $id );
				$name = $existing ? (string) ( $existing->name ?? '' ) : '';
			} else {
				$name = 'Inventory ' . current_time( 'Y-m-d' );
			}
			$raw_items = isset( $_POST['items'] ) && is_array( $_POST['items'] ) ? wp_unslash( $_POST['items'] ) : array();
			$one_time_items = isset( $_POST['one_time_items'] ) && is_array( $_POST['one_time_items'] ) ? wp_unslash( $_POST['one_time_items'] ) : array();
			$no_supplier_items = isset( $_POST['no_supplier_items'] ) && is_array( $_POST['no_supplier_items'] ) ? wp_unslash( $_POST['no_supplier_items'] ) : array();
			$user = wp_get_current_user();
			$is_rc_user = in_array( RC_Roles::ROLE_USER, (array) $user->roles, true );
			$items = array();
			foreach ( $raw_items as $row ) {
				$items[] = array(
					'ingredient_name' => sanitize_text_field( (string) ( $row['ingredient_name'] ?? '' ) ),
					'supplier_name' => $is_rc_user ? sanitize_text_field( (string) ( $existing->supplier_name ?? '' ) ) : sanitize_text_field( (string) ( $row['supplier_name'] ?? '' ) ),
					'min_level' => $is_rc_user ? (int) ( $existing->min_level ?? 0 ) : (int) ( $row['min_level'] ?? 0 ),
					'order_qty' => (float) ( $row['order_qty'] ?? 0 ),
				);
			}
			foreach ( $one_time_items as $row ) {
				$name = sanitize_text_field( (string) ( $row['name'] ?? '' ) );
				if ( '' === $name ) { continue; }
				$items[] = array(
					'ingredient_name' => $name, 'supplier_name' => '',
					'min_level' => 0, 'order_qty' => (float) ( $row['order_qty'] ?? 0 ),
				);
			}
			foreach ( $no_supplier_items as $row ) {
				$name = sanitize_text_field( (string) ( $row['name'] ?? '' ) );
				if ( '' === $name ) { continue; }
				$items[] = array(
					'ingredient_name' => $name, 'supplier_name' => '',
					'min_level' => (int) ( $row['min_level'] ?? 0 ),
					'order_qty' => (float) ( $row['order_qty'] ?? 0 ),
				);
			}
			if ( $id > 0 ) {
				RC_Inventory_Repository::update( $id, $name, $items );
			} else {
				$id = RC_Inventory_Repository::create( $name, $items );
			}
			wp_send_json_success( array( 'message' => __( 'Inventory saved.', 'restaurant-costing' ) ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function handle_delete_inventory(): void {
		if ( ! current_user_can( RC_Roles::CAP_ACCESS_INVENTORY ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$user = wp_get_current_user();
		if ( in_array( RC_Roles::ROLE_USER, (array) $user->roles, true ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		check_admin_referer( 'rc_delete_inventory_' . $id );

		try {
			if ( $id > 0 ) {
				RC_Inventory_Repository::delete( $id );
			}

			RC_Notice::success( __( 'Inventory deleted.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_print_inventory(): void {
		if ( ! current_user_can( RC_Roles::CAP_PRINT ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$id          = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		$hide_blank  = ! empty( $_GET['hide_blank'] );
		$unemailed   = ! empty( $_GET['unemailed'] );
		$supplier    = isset( $_GET['supplier'] ) ? sanitize_text_field( wp_unslash( $_GET['supplier'] ) ) : null;
		$prep_list   = ! empty( $_GET['prep_list'] );

		if ( ! wp_verify_nonce( (string) ( $_GET['_wpnonce'] ?? '' ), 'rc_inventory_ajax' ) ) {
			RC_Notice::error( __( 'Security check failed.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		try {
			$inventory = RC_Inventory_Repository::get( $id );
			if ( ! $inventory ) {
				RC_Notice::error( __( 'Inventory not found.', 'restaurant-costing' ) );
				wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
				exit;
			}

			if ( $unemailed ) {
				$items = RC_Inventory_Repository::get_unemailed_items( $id );
			} else {
				$items = RC_Inventory_Repository::get_items( $id );
			}
			$ing_table     = RC_Database::table( 'ingredients' );
			$area_table    = RC_Database::table( 'areas' );

			// Enrich items with area data from current ingredients.
			global $wpdb;
			$names = array();
			foreach ( $items as $item ) {
				$name = (string) ( $item->ingredient_name ?? '' );
				if ( '' !== $name ) {
					$names[] = $name;
				}
			}
			if ( ! empty( $names ) ) {
				$placeholders = implode( ',', array_fill( 0, count( $names ), '%s' ) );
				$area_data    = $wpdb->get_results(
					$wpdb->prepare(
						"SELECT i.name AS ingredient_name, i.area, i.is_recipe, a.name AS area_name
						FROM {$ing_table} i
						LEFT JOIN {$area_table} a ON a.name = i.area
						WHERE i.name IN ({$placeholders})",
						$names
					)
				) ?: array();
				$area_map = array();
				foreach ( $area_data as $row ) {
					$area_map[ $row->ingredient_name ] = array(
						'area'      => $row->area,
						'area_name' => $row->area_name,
						'is_recipe' => $row->is_recipe,
					);
				}
				foreach ( $items as $item ) {
					$name = (string) ( $item->ingredient_name ?? '' );
					if ( isset( $area_map[ $name ] ) ) {
						$item->area      = $area_map[ $name ]['area'];
						$item->area_name = $area_map[ $name ]['area_name'];
						$item->is_recipe = $area_map[ $name ]['is_recipe'];
					} else {
						$item->area      = '';
						$item->area_name = null;
						$item->is_recipe = 0;
					}
				}
			}

			// Sort by supplier, then ingredient name.
			usort( $items, static function ( $a, $b ) {
				$sa = (string) ( $a->supplier_name ?? $a->supplier ?? '' );
				$sb = (string) ( $b->supplier_name ?? $b->supplier ?? '' );
				if ( $sa !== $sb ) {
					return strcasecmp( $sa, $sb );
				}
				return strcasecmp( (string) ( $a->ingredient_name ?? '' ), (string) ( $b->ingredient_name ?? '' ) );
			} );

			if ( null !== $supplier ) {
				$items = array_filter( $items, static function ( $item ) use ( $supplier ) {
					$item_sup = (string) ( $item->supplier_name ?? $item->supplier ?? '' );
					if ( '' === $supplier ) {
						return '' === $item_sup;
					}
					return strcasecmp( $item_sup, $supplier ) === 0;
				} );
			}

			if ( $prep_list ) {
				$items = array_filter( $items, static function ( $item ) {
					return ! empty( $item->is_recipe );
				} );
			}

			if ( $hide_blank ) {
				$items = array_filter( $items, static function ( $item ) {
					return (float) ( $item->order_qty ?? 0 ) > 0 || (int) ( $item->min_level ?? 0 ) > 0;
				} );
			}

			require RC_PLUGIN_DIR . 'admin/views/inventory-print.php';
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-inventory' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function ajax_send_order_email(): void {
		check_ajax_referer( 'rc_inventory_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_EMAIL_ORDER ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$inventory_id   = isset( $_POST['inventory_id'] ) ? (int) $_POST['inventory_id'] : 0;
			$supplier_name  = isset( $_POST['supplier_name'] ) ? sanitize_text_field( (string) $_POST['supplier_name'] ) : '';

			if ( ! $inventory_id || '' === $supplier_name ) {
				wp_send_json_error( array( 'message' => __( 'Invalid request.', 'restaurant-costing' ) ) );
			}

			$supplier  = RC_Supplier_Repository::get_by_name( $supplier_name );
			$items     = RC_Inventory_Repository::get_supplier_items( $inventory_id, $supplier_name );

			if ( empty( $items ) ) {
				wp_send_json_error( array( 'message' => __( 'No items to order for this supplier.', 'restaurant-costing' ) ) );
			}

			$to = $supplier ? $supplier->email : '';
			if ( '' === $to ) {
				wp_send_json_error( array( 'message' => __( 'Supplier has no email address. Set it in Suppliers.', 'restaurant-costing' ) ) );
			}

			$subject = sprintf( __( 'Order from %s', 'restaurant-costing' ), get_bloginfo( 'name' ) );
			$body    = sprintf( "<h2>%s</h2><p>%s</p>", esc_html( $subject ), esc_html( sprintf( __( 'Inventory: %s', 'restaurant-costing' ), $inventory_id ) ) );
			$body   .= '<table border="1" cellpadding="8" cellspacing="0" class="rc-w-100" style="border-collapse:collapse;">';
			$body   .= '<thead><tr><th>' . esc_html__( 'Item', 'restaurant-costing' ) . '</th><th>' . esc_html__( 'Quantity', 'restaurant-costing' ) . '</th></tr></thead><tbody>';

			foreach ( $items as $item ) {
				$body .= '<tr>';
				$body .= '<td>' . esc_html( (string) ( $item->ingredient_name ?? '' ) ) . '</td>';
				$body .= '<td>' . esc_html( number_format( (float) ( $item->order_qty ?? 0 ), 2 ) ) . '</td>';
				$body .= '</tr>';
			}

			$body .= '</tbody></table>';

			$sent = wp_mail( $to, $subject, $body, array( 'Content-Type: text/html; charset=UTF-8' ) );

			if ( $sent ) {
				RC_Inventory_Repository::mark_emailed( $inventory_id, $supplier_name );
			}

			if ( $sent ) {
				wp_send_json_success( array( 'message' => __( 'Order email sent.', 'restaurant-costing' ) ) );
			} else {
				wp_send_json_error( array( 'message' => __( 'Failed to send email.', 'restaurant-costing' ) ) );
			}
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function handle_save_area(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_AREAS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-areas' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_save_area' );

		$id      = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
		$name    = isset( $_POST['name'] ) ? sanitize_text_field( (string) $_POST['name'] ) : '';
		$add_new = ! empty( $_POST['add_new'] );
		$is_modal = ! empty( $_POST['rc_modal'] );

		if ( '' === $name ) {
			RC_Notice::error( __( 'Area name is required.', 'restaurant-costing' ) );
			$args = array( 'page' => 'restaurant-costing-areas' );
			if ( $is_modal ) {
				$args['rc_modal'] = '1';
			}
			wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
			exit;
		}

		try {
			if ( ! $id ) {
				global $wpdb;
				$max = (int) $wpdb->get_var( "SELECT COALESCE(MAX(sort_order), 0) FROM " . RC_Database::table( 'areas' ) );
			}

			RC_Area_Repository::upsert( array(
				'name'       => $name,
				'sort_order' => $id ? null : ( $max + 1 ),
			), $id > 0 ? $id : null );

			RC_Notice::success( __( 'Area saved.', 'restaurant-costing' ) );
			$args = array( 'page' => 'restaurant-costing-areas' );
			if ( $add_new ) {
				$args['rc_add_new'] = '1';
			}
			if ( $is_modal ) {
				$args['rc_modal'] = '1';
			}
			wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			$args = array( 'page' => 'restaurant-costing-areas' );
			if ( $is_modal ) {
				$args['rc_modal'] = '1';
			}
			wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_delete_area(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_AREAS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-areas' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		$is_modal = ! empty( $_GET['rc_modal'] );
		check_admin_referer( 'rc_delete_area_' . $id );

		try {
			if ( $id > 0 ) {
				RC_Area_Repository::delete( $id );
			}

			RC_Notice::success( __( 'Area deleted.', 'restaurant-costing' ) );
			$args = array( 'page' => 'restaurant-costing-areas' );
			if ( $is_modal ) {
				$args['rc_modal'] = '1';
			}
			wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			$args = array( 'page' => 'restaurant-costing-areas' );
			if ( $is_modal ) {
				$args['rc_modal'] = '1';
			}
			wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_move_area(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_AREAS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-areas' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$id   = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		$dir  = isset( $_GET['dir'] ) ? sanitize_text_field( (string) $_GET['dir'] ) : '';
		$is_modal = ! empty( $_GET['rc_modal'] );
		check_admin_referer( 'rc_move_area_' . $id );

		try {
			if ( $id > 0 ) {
				if ( 'up' === $dir ) {
					RC_Area_Repository::move_up( $id );
				} elseif ( 'down' === $dir ) {
					RC_Area_Repository::move_down( $id );
				}
			}
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			$args = array( 'page' => 'restaurant-costing-areas' );
			if ( $is_modal ) {
				$args['rc_modal'] = '1';
			}
			wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
			exit;
		}

		$args = array( 'page' => 'restaurant-costing-areas' );
		if ( $is_modal ) {
			$args['rc_modal'] = '1';
		}
		wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
		exit;
	}

	public function handle_save_display_settings(): void {
		check_admin_referer( 'rc_save_display_settings' );
		update_option( 'rc_hide_nutrition', ! empty( $_POST['hide_nutrition'] ) ? 1 : 0 );
		update_option( 'rc_show_sal', ! empty( $_POST['show_sal'] ) ? 1 : 0 );

		RC_Notice::success( __( 'Display settings saved.', 'restaurant-costing' ) );
		wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
		exit;
	}

	public function ajax_save_display_settings(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		update_option( 'rc_hide_nutrition', ! empty( $_POST['hide_nutrition'] ) ? 1 : 0 );
		update_option( 'rc_show_sal', ! empty( $_POST['show_sal'] ) ? 1 : 0 );

		wp_send_json_success( array( 'message' => __( 'Display settings saved.', 'restaurant-costing' ) ) );
	}

	public function admin_body_theme_class( string $classes ): string {
		$theme = isset( $_GET['rc_theme'] ) ? sanitize_key( $_GET['rc_theme'] ) : ( isset( $_COOKIE['rc_theme'] ) ? sanitize_key( $_COOKIE['rc_theme'] ) : get_option( 'rc_theme', 'dark' ) );
		if ( 'light' === $theme ) {
			$classes .= ' rc-theme-light';
		} else {
			$classes .= ' rc-theme-dark';
		}
		return $classes;
	}

	public function ajax_save_theme(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		$theme = isset( $_POST['theme'] ) && 'light' === $_POST['theme'] ? 'light' : 'dark';
		update_option( 'rc_theme', $theme );
		setcookie( 'rc_theme', $theme, time() + 365 * 24 * 3600, '/' );

		wp_send_json_success( array( 'message' => __( 'Theme saved.', 'restaurant-costing' ), 'theme' => $theme ) );
	}

	public function ajax_get_inventory_data(): void {
		check_ajax_referer( 'rc_inventory_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_ACCESS_INVENTORY ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			global $wpdb;
			$ingredients_table = RC_Database::table( 'ingredients' );
			$areas_table       = RC_Database::table( 'areas' );
			$suppliers_table   = RC_Database::table( 'suppliers' );

			$inventory_id = isset( $_GET['inventory_id'] ) ? (int) $_GET['inventory_id'] : 0;

			$areas = RC_Area_Repository::get_all();

			// Build email_order map from suppliers for JS lookup.
			$supplier_rows = $wpdb->get_results( "SELECT business_name, email_order FROM {$suppliers_table}" ) ?: array();
			$email_order_map = array();
			foreach ( $supplier_rows as $row ) {
				$email_order_map[ $row->business_name ] = (int) $row->email_order;
			}

			if ( $inventory_id > 0 ) {
				// Static snapshot: return only saved items
				$saved_items = RC_Inventory_Repository::get_items( $inventory_id );
				$ingredients = array();
				$one_time    = array();

				foreach ( $saved_items as $item ) {
					if ( '' === (string) ( $item->supplier_name ?? '' ) ) {
						$one_time[] = $item;
						continue;
					}
					$ing = $wpdb->get_row(
						$wpdb->prepare(
							"SELECT i.id, i.name, i.supplier, i.min_level, i.area,
							 i.is_recipe,
							 COALESCE(a.sort_order, 9999) AS area_sort,
							 COALESCE(a.name, i.area) AS area_name
							 FROM {$ingredients_table} i
							 LEFT JOIN {$areas_table} a ON a.name = i.area
							 WHERE i.name = %s
							 LIMIT 1",
							$item->ingredient_name
						)
					);
					if ( $ing ) {
						$ing->order_qty    = $item->order_qty;
						$ing->emailed      = $item->emailed ?? 0;
						$ing->email_order  = $email_order_map[ $item->supplier_name ] ?? 0;
					} else {
						$ing = (object) array(
							'id'           => 0,
							'name'         => $item->ingredient_name,
							'supplier'     => $item->supplier_name,
							'min_level'    => $item->min_level,
							'area'         => '',
							'is_recipe'    => 0,
							'area_sort'    => 9999,
							'area_name'    => '',
							'order_qty'    => $item->order_qty,
							'emailed'      => $item->emailed ?? 0,
							'email_order'  => $email_order_map[ $item->supplier_name ] ?? 0,
						);
					}
					$ingredients[] = $ing;
				}

				wp_send_json_success( array(
					'ingredients' => $ingredients,
					'saved_items' => $one_time,
					'areas'       => $areas,
					'is_edit'     => true,
				) );
			} else {
				// New inventory: return all current ingredients
				$ingredients = $wpdb->get_results(
					"SELECT i.id, i.name, i.supplier, i.min_level, i.area,
					 i.is_recipe,
					 COALESCE(a.sort_order, 9999) AS area_sort,
					 COALESCE(a.name, i.area) AS area_name
					 FROM {$ingredients_table} i
					 LEFT JOIN {$areas_table} a ON a.name = i.area
					 ORDER BY area_sort ASC, i.name ASC"
				) ?: array();

				// Attach email_order for new inventories.
				foreach ( $ingredients as $ing ) {
					$ing->email_order = $email_order_map[ $ing->supplier ] ?? 0;
					$ing->emailed     = 0;
				}

				wp_send_json_success( array(
					'ingredients' => $ingredients,
					'saved_items' => array(),
					'areas'       => $areas,
					'is_edit'     => false,
				) );
			}
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_search_ingredients(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_VIEW_INGREDIENTS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$term = isset( $_GET['term'] ) ? sanitize_text_field( wp_unslash( $_GET['term'] ) ) : '';
			if ( strlen( $term ) < 2 ) {
				wp_send_json_error( array( 'message' => __( 'Search term too short.', 'restaurant-costing' ) ) );
			}

			$results = RC_Ingredient_Repository::search( $term );
			wp_send_json( $results );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_toggle_print_image(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );
		if ( ! current_user_can( RC_Roles::CAP_EDIT_RECIPES ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}
		$recipe_id   = isset( $_POST['recipe_id'] ) ? (int) $_POST['recipe_id'] : 0;
		$print_image = isset( $_POST['print_image'] ) ? (int) $_POST['print_image'] : 0;
		if ( $recipe_id < 1 ) {
			wp_send_json_error( array( 'message' => __( 'Invalid recipe ID.', 'restaurant-costing' ) ) );
		}
		try {
			global $wpdb;
			$wpdb->update(
				RC_Database::table( 'recipes' ),
				array( 'print_image' => $print_image ),
				array( 'id' => $recipe_id ),
				array( '%d' ),
				array( '%d' )
			);
			RC_Exception::throw_on_db_error( 'update', RC_Database::table( 'recipes' ) );
			wp_send_json_success();
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	private static function _sort_inventory_by_supplier( array $items ): array {
		usort( $items, static function ( $a, $b ) {
			$sa = strtolower( (string) ( $a->supplier_name ?? '' ) );
			$sb = strtolower( (string) ( $b->supplier_name ?? '' ) );
			if ( $sa !== $sb ) {
				return $sa <=> $sb;
			}
			return strcasecmp( (string) ( $a->ingredient_name ?? '' ), (string) ( $b->ingredient_name ?? '' ) );
		} );
		return $items;
	}

	public function handle_toggle_recipe_status(): void {
		if ( ! current_user_can( RC_Roles::CAP_EDIT_RECIPES ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$recipe_id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		check_admin_referer( 'rc_toggle_recipe_' . $recipe_id );

		try {
			if ( $recipe_id > 0 ) {
				$recipe = RC_Recipe_Repository::get( $recipe_id );
				if ( ! $recipe ) {
					wp_safe_redirect( admin_url( 'admin.php?page=restaurant-costing-recipes' ) );
					exit;
				}

				$is_active = (int) ( $recipe->is_active ?? 1 );
				if ( 0 === $is_active ) {
					global $wpdb;
					$table      = RC_Database::table( 'recipes' );
					$normalized = RC_Name_Matcher::normalize( (string) $recipe->name );
					$conflict   = $wpdb->get_var( $wpdb->prepare(
						"SELECT id FROM {$table} WHERE name_normalized = %s AND is_active = 1 AND id != %d LIMIT 1",
						$normalized,
						$recipe_id
					) );

					if ( $conflict ) {
						RC_Notice::error( __( 'Cannot reactivate: an active recipe with this name already exists. Please rename first.', 'restaurant-costing' ) );
						wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
						exit;
					}
				}

				global $wpdb;
				$table = RC_Database::table( 'recipes' );
				$wpdb->query( $wpdb->prepare( "UPDATE {$table} SET is_active = (1 - is_active) WHERE id = %d", $recipe_id ) );
			}
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		wp_safe_redirect(
			isset( $_SERVER['HTTP_REFERER'] )
				? sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ) )
				: admin_url( 'admin.php?page=restaurant-costing-recipes' )
		);
		exit;
	}

	public function handle_print_recipe(): void {
		if ( ! current_user_can( RC_Roles::CAP_PRINT ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$recipe_id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;

		try {
			$recipe = RC_Recipe_Repository::get_full( $recipe_id );

			if ( ! $recipe ) {
				RC_Notice::error( __( 'Recipe not found.', 'restaurant-costing' ) );
				wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
				exit;
			}

			$is_scaled = isset( $_GET['print_scaled'] ) && $_GET['print_scaled'] === '1';
			if ( $is_scaled ) {
				$recipe['recipe']->scale_factor = (float) $recipe['recipe']->scale_factor;
			} else {
				$recipe['recipe']->scale_factor = 1.0;
			}

			$data = $recipe;
			require RC_PLUGIN_DIR . 'admin/views/recipe-print.php';
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_copy_recipe(): void {
		if ( ! current_user_can( RC_Roles::CAP_EDIT_RECIPES ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$source_id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		check_admin_referer( 'rc_copy_recipe_' . $source_id );

		try {
			$source = $source_id > 0 ? RC_Recipe_Repository::get_full( $source_id ) : null;
			if ( ! $source ) {
				RC_Notice::error( __( 'Recipe not found.', 'restaurant-costing' ) );
				wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
				exit;
			}

			$key = 'rc_recipe_copy_' . wp_generate_password( 16, false );
			$data = array(
				'portions'      => $source['recipe']->portions,
				'scale_factor'  => $source['recipe']->scale_factor,
				'menu_price'    => $source['recipe']->menu_price,
				'labor_rate'    => $source['recipe']->labor_rate,
				'method'        => $source['recipe']->method,
				'image_url'     => $source['recipe']->image_url ?? '',
				'print_image'   => (int) ( $source['recipe']->print_image ?? 0 ),
				'lines'         => array_map( static function ( $line ): array {
					return array(
						'ingredient_id'    => (int) $line->ingredient_id,
						'ingredient_name'  => (string) $line->ingredient_name,
						'quantity'         => (float) $line->quantity,
						'unit'             => (string) $line->unit,
						'density_g_per_ml' => $line->density_g_per_ml,
					);
				}, $source['lines'] ),
				'labor'         => array_map( static function ( $lab ): array {
					return array(
						'item_label' => (string) ( $lab->item_label ?? '' ),
						'minutes'    => (float) ( $lab->minutes ?? 0 ),
						'quantity'   => (float) ( $lab->quantity ?? 1 ),
					);
				}, $source['labor'] ),
			);

			set_transient( $key, $data, HOUR_IN_SECONDS );

			wp_safe_redirect(
				add_query_arg(
					array(
						'page'    => 'restaurant-costing-recipe-edit',
						'rc_copy' => $key,
					),
					admin_url( 'admin.php' )
				)
			);
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_save_recipe(): void {
		if ( ! current_user_can( RC_Roles::CAP_EDIT_RECIPES ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_save_recipe' );

		$id = isset( $_POST['recipe_id'] ) ? (int) $_POST['recipe_id'] : 0;

		if ( ! RC_License::can_create_recipe( $id ) ) {
			RC_Notice::error( __( 'Demo limit reached. You can save up to 10 recipes. Upgrade to the full version to unlock unlimited recipes.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		try {
			$name = sanitize_text_field( (string) ( $_POST['name'] ?? '' ) );

			$image_url = sanitize_text_field( (string) ( $_POST['image_url'] ?? '' ) );

			$is_active = 1;
			if ( $id > 0 ) {
				$existing = RC_Recipe_Repository::get( $id );
				if ( $existing ) {
					$is_active = (int) ( $existing->is_active ?? 1 );
				}
			}

			$data = array(
				'id'            => $id,
				'name'          => $name,
				'portions'      => $_POST['portions'] ?? 1,
				'scale_factor'  => $_POST['scale_factor'] ?? 1,
				'menu_price'    => $_POST['menu_price'] ?? '',
				'labor_rate'    => $_POST['labor_rate'] ?? 25,
				'method'        => $_POST['method'] ?? '',
				'is_ingredient' => ! empty( $_POST['is_ingredient'] ),
				'image_url'     => $image_url,
				'print_image'   => ! empty( $_POST['print_image'] ),
				'is_active'     => $is_active,
				'lines'         => isset( $_POST['lines'] ) && is_array( $_POST['lines'] ) ? wp_unslash( $_POST['lines'] ) : array(),
				'labor'         => isset( $_POST['labor'] ) && is_array( $_POST['labor'] ) ? wp_unslash( $_POST['labor'] ) : array(),
			);

			$id = RC_Recipe_Repository::save( $data );

			RC_Notice::success( __( 'Recipe saved.', 'restaurant-costing' ) );
			wp_safe_redirect(
				add_query_arg(
					array(
						'page' => 'restaurant-costing-recipe-edit',
						'id'   => $id,
					),
					admin_url( 'admin.php' )
				)
			);
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_delete_recipe(): void {
		if ( ! current_user_can( RC_Roles::CAP_EDIT_RECIPES ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		check_admin_referer( 'rc_delete_recipe_' . $id );

		try {
			if ( $id > 0 ) {
				RC_Recipe_Repository::delete( $id );
			}

			RC_Notice::success( __( 'Recipe deleted.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-recipes' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function ajax_save_ingredient(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_EDIT_INGREDIENTS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$numeric_fields = array(
				'calories', 'fat_grams', 'saturated_fat_grams', 'trans_fat_grams',
				'carb_grams', 'fiber_grams', 'sugar_grams', 'cholesterol_mg',
				'protein_grams', 'sodium_mg', 'package_cost',
				'vit_a', 'vit_c_mg', 'potassium_mg', 'calcium_mg', 'iron_mg',
			);

			$input = array(
				'id'             => isset( $_POST['id'] ) ? (int) $_POST['id'] : null,
				'name'           => $_POST['name'] ?? '',
				'food_type'      => $_POST['food_type'] ?? '',
				'unit'           => $_POST['unit'] ?? 'g',
				'weight_grams'   => $_POST['weight_grams'] ?? 0,
				'food_cost'      => $_POST['food_cost'] ?? 0,
				'min_level'      => isset( $_POST['min_level'] ) ? (int) $_POST['min_level'] : 0,
				'size_label'     => $_POST['size_label'] ?? '',
				'supplier'       => $_POST['supplier'] ?? '',
				'area'           => $_POST['area'] ?? '',
				'package_size'   => $_POST['package_size'] ?? '',
				'ingredients_text' => $_POST['ingredients_text'] ?? null,
			);

			if ( null === $input['id'] && ! RC_License::can_create_ingredient() ) {
				wp_send_json_error( array( 'message' => __( 'Demo limit reached. You can add up to 50 ingredients. Upgrade to the full version to unlock unlimited ingredients.', 'restaurant-costing' ) ) );
			}

			if ( null !== $input['id'] ) {
				$existing = RC_Ingredient_Repository::get( $input['id'] );
				if ( $existing && (int) ( $existing->is_recipe ?? 0 ) === 1 ) {
					wp_send_json_error( array( 'message' => __( 'Sub-recipe ingredients are managed by their source recipe and cannot be edited here.', 'restaurant-costing' ) ) );
				}
			}

			foreach ( $numeric_fields as $field ) {
				$val = $_POST[ $field ] ?? '';
				$input[ $field ] = ( '' === (string) $val ) ? null : (float) $val;
			}

			$result = RC_Ingredient_Repository::create_quick( $input );

			if ( ! $result['success'] ) {
				wp_send_json_error( array( 'message' => $result['message'] ?? __( 'Save failed.', 'restaurant-costing' ) ) );
			}

			wp_send_json_success( $result );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_delete_ingredient(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_EDIT_INGREDIENTS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
			if ( $id === 0 ) {
				wp_send_json_error( array( 'message' => __( 'Invalid ingredient ID.', 'restaurant-costing' ) ) );
			}

			$result = RC_Ingredient_Repository::delete( $id );

			if ( ! $result ) {
				wp_send_json_error( array( 'message' => __( 'Could not delete ingredient.', 'restaurant-costing' ) ) );
			}

			wp_send_json_success( array( 'message' => __( 'Ingredient deleted.', 'restaurant-costing' ) ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_add_food_type(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_EDIT_INGREDIENTS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$name = isset( $_POST['name'] ) ? sanitize_text_field( (string) $_POST['name'] ) : '';
			if ( '' === $name ) {
				wp_send_json_error( array( 'message' => __( 'Food type name is required.', 'restaurant-costing' ) ) );
			}

			$id = RC_Food_Type_Repository::find_or_create( $name );

			wp_send_json_success( array( 'id' => $id, 'name' => $name ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_get_ingredient(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_EDIT_INGREDIENTS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
			if ( $id === 0 ) {
				wp_send_json_error( array( 'message' => __( 'Invalid ingredient ID.', 'restaurant-costing' ) ) );
			}

			$ingredient = RC_Ingredient_Repository::get( $id );
			if ( ! $ingredient ) {
				wp_send_json_error( array( 'message' => __( 'Ingredient not found.', 'restaurant-costing' ) ) );
			}

			if ( ! empty( $ingredient->is_recipe ) ) {
				global $wpdb;
				$recipe_id = $wpdb->get_var(
					$wpdb->prepare(
						"SELECT id FROM {$wpdb->prefix}rc_recipes WHERE name_normalized = %s LIMIT 1",
						$ingredient->name_normalized ?? ''
					)
				);
				$ingredient->recipe_id = $recipe_id ? (int) $recipe_id : 0;
			}

			wp_send_json_success( $ingredient );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_check_recipe_name(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_VIEW_RECIPES ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
			$id   = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;

			if ( '' === $name ) {
				wp_send_json_error( array( 'message' => __( 'Recipe name is required.', 'restaurant-costing' ) ) );
			}

			$duplicate = RC_Recipe_Repository::check_duplicate_name( $name, $id );
			wp_send_json_success( array( 'duplicate' => $duplicate ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_calculate_recipe(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_VIEW_RECIPES ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$recipe = (object) array(
				'portions'     => (float) ( $_POST['portions'] ?? 1 ),
				'scale_factor' => (float) ( $_POST['scale_factor'] ?? 1 ),
				'menu_price'   => '' === (string) ( $_POST['menu_price'] ?? '' ) ? null : (float) $_POST['menu_price'],
				'labor_rate'   => (float) ( $_POST['labor_rate'] ?? 25 ),
			);

			$lines = array();
			if ( isset( $_POST['lines'] ) && is_array( $_POST['lines'] ) ) {
				foreach ( wp_unslash( $_POST['lines'] ) as $line ) {
					if ( empty( $line['ingredient_name'] ) ) {
						continue;
					}
					$lines[] = (object) array(
						'ingredient_id'    => ! empty( $line['ingredient_id'] ) ? (int) $line['ingredient_id'] : null,
						'ingredient_name'  => sanitize_text_field( (string) $line['ingredient_name'] ),
						'quantity'         => (float) ( $line['quantity'] ?? 0 ),
						'unit'             => in_array( $line['unit'] ?? 'g', array( 'g', 'ml', 'ea' ), true ) ? $line['unit'] : 'g',
						'density_g_per_ml' => isset( $line['density_g_per_ml'] ) && '' !== (string) $line['density_g_per_ml']
							? (float) $line['density_g_per_ml']
							: null,
					);
				}
			}

			$labor = array();
			if ( isset( $_POST['labor'] ) && is_array( $_POST['labor'] ) ) {
				foreach ( wp_unslash( $_POST['labor'] ) as $lab ) {
					$labor[] = (object) array(
						'item_label' => sanitize_text_field( (string) ( $lab['item_label'] ?? '' ) ),

						'minutes'    => (float) ( $lab['minutes'] ?? 0 ),
						'quantity'   => max( 0, (float) ( $lab['quantity'] ?? 1 ) ),
					);
				}
			}

			wp_send_json_success( RC_Recipe_Calculator::calculate( $recipe, $lines, $labor ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function handle_import(): void {
		if ( ! current_user_can( RC_Roles::CAP_IMPORT ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_import_foodmaster' );

		try {
			$skip_existing = ! empty( $_POST['skip_existing'] );
			$path   = ( new RC_Importer() )->get_default_csv_path();
			$result = ( new RC_Importer() )->import_foodmaster_internal( $path, $skip_existing );

			if ( $result['errors'] > 0 ) {
				RC_Notice::warning( (string) $result['message'] );
			} else {
				RC_Notice::success( (string) $result['message'] );
			}

			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_upload_foodmaster(): void {
		if ( ! current_user_can( RC_Roles::CAP_IMPORT ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_upload_foodmaster' );

		if ( empty( $_FILES['foodmaster_csv'] ) || UPLOAD_ERR_OK !== $_FILES['foodmaster_csv']['error'] ) {
			RC_Notice::error( __( 'No file uploaded or upload failed.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$file     = $_FILES['foodmaster_csv'];
		$tmp_name = $file['tmp_name'];

		$ext = strtolower( pathinfo( (string) $file['name'], PATHINFO_EXTENSION ) );
		if ( 'csv' !== $ext ) {
			RC_Notice::error( __( 'Only .csv files are allowed.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$target = RC_PLUGIN_DIR . 'data/foodmaster.csv';

		if ( ! move_uploaded_file( $tmp_name, $target ) ) {
			RC_Notice::error( __( 'Could not save uploaded file.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		RC_Notice::success( __( 'foodmaster.csv uploaded successfully. You may now re-import it below.', 'restaurant-costing' ) );
		wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
		exit;
	}

	public function handle_backup(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_backup_database' );

		global $wpdb;
		$suffixes = RC_Database::all_table_suffixes();
		$sqldump  = "-- Restaurant Costing backup\n\n";

		foreach ( $suffixes as $suffix ) {
			$table = RC_Database::table( $suffix );
			$sqldump .= "DROP TABLE IF EXISTS `{$table}`;\n";

			$row = $wpdb->get_row( "SHOW CREATE TABLE `{$table}`", ARRAY_N );
			if ( $row && isset( $row[1] ) ) {
				$sqldump .= $row[1] . ";\n\n";
			}

			$data = $wpdb->get_results( "SELECT * FROM `{$table}`", ARRAY_A );
			if ( ! empty( $data ) ) {
				$columns  = array_keys( $data[0] );
				$col_list = '`' . implode( '`, `', $columns ) . '`';

			foreach ( $data as $record ) {
					$values = array();
					foreach ( $record as $val ) {
						if ( null === $val ) {
							$values[] = 'NULL';
						} else {
							$values[] = $wpdb->prepare( '%s', $val );
						}
					}
					$sqldump .= "INSERT INTO `{$table}` ({$col_list}) VALUES (" . implode( ', ', $values ) . ");\n";
				}
			}
			$sqldump .= "\n";
		}

		$filename = 'rc-backup-' . gmdate( 'Y-m-d' ) . '.sql.gz';
		$gzdata   = gzencode( $sqldump, 9 );

		header( 'Content-Type: application/x-gzip' );
		header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
		header( 'Content-Length: ' . strlen( $gzdata ) );
		echo $gzdata;
		exit;
	}

	public function handle_restore(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_restore_database' );

		if ( empty( $_FILES['rc_backup_file'] ) || UPLOAD_ERR_OK !== $_FILES['rc_backup_file']['error'] ) {
			RC_Notice::error( __( 'No file uploaded or upload failed.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$file     = $_FILES['rc_backup_file'];
		$tmp_name = $file['tmp_name'];

		$ext = strtolower( pathinfo( (string) $file['name'], PATHINFO_EXTENSION ) );
		if ( 'gz' !== $ext ) {
			RC_Notice::error( __( 'Only .sql.gz backup files are allowed.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$gzipped = file_get_contents( $tmp_name );
		if ( false === $gzipped ) {
			RC_Notice::error( __( 'Could not read uploaded file.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$sql = gzdecode( $gzipped );
		if ( false === $sql ) {
			RC_Notice::error( __( 'Could not decompress backup file.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		if ( ! str_starts_with( $sql, '-- Restaurant Costing backup' ) ) {
			RC_Notice::error( __( 'Not a valid Restaurant Costing backup file.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		global $wpdb;
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';

		$suffixes = RC_Database::all_table_suffixes();
		$valid_tables = array();
		foreach ( $suffixes as $suf ) {
			$valid_tables[ RC_Database::table( $suf ) ] = $suf;
		}

		RC_Database::drop_all_tables();

		$sql = str_replace( "\r\n", "\n", $sql );
		$statements = array_filter( array_map( 'trim', explode( ";\n", $sql ) ) );
		foreach ( $statements as $st ) {
			if ( '' === $st ) {
				continue;
			}

			if ( ! preg_match( '/^(DROP\s+TABLE\s+IF\s+EXISTS|CREATE\s+TABLE|INSERT\s+INTO)\s+`([^`]+)`/i', $st, $m ) ) {
				continue;
			}

			$table_name = $m[2];

			$matched_current = null;
			foreach ( $valid_tables as $current_name => $suf ) {
				if ( str_ends_with( $table_name, 'rc_' . $suf ) ) {
					$matched_current = $current_name;
					break;
				}
			}

			if ( null === $matched_current ) {
				continue;
			}

			if ( $table_name !== $matched_current ) {
				$st = preg_replace( '/`' . preg_quote( $table_name, '/' ) . '`/', '`' . $matched_current . '`', $st, 1 );
			}

			$wpdb->query( $st );
		}

		delete_option( 'rc_db_version' );
		delete_option( 'rc_activation_import' );
		update_option( 'rc_db_version', RC_DB_VERSION );

		if ( RC_License::is_demo() ) {
			$over = RC_License::over_cap_counts();
			$warnings = array();
			if ( isset( $over['ingredients'] ) ) {
				$warnings[] = sprintf( __( '%d ingredient(s) exceed the demo limit.', 'restaurant-costing' ), $over['ingredients'] );
			}
			if ( isset( $over['recipes'] ) ) {
				$warnings[] = sprintf( __( '%d recipe(s) exceed the demo limit.', 'restaurant-costing' ), $over['recipes'] );
			}
			if ( ! empty( $warnings ) ) {
				RC_Notice::warning( __( 'Demo mode: ', 'restaurant-costing' ) . implode( ' ', $warnings ) . ' ' . __( 'Existing data is accessible, but you cannot add more. Upgrade to unlock.', 'restaurant-costing' ) );
			}
		}

		RC_Notice::success( __( 'Database restored successfully.', 'restaurant-costing' ) );
		wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
		exit;
	}

	public function handle_delete_all_data(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_delete_all_data' );

		RC_Database::drop_all_tables();
		RC_Database::create_tables();

		delete_option( 'rc_db_version' );
		delete_option( 'rc_activation_import' );
		delete_option( 'rc_last_foodmaster_import' );
		update_option( 'rc_db_version', RC_DB_VERSION );

		RC_Notice::success( __( 'All Restaurant Costing data has been deleted. Tables have been recreated and are ready for use.', 'restaurant-costing' ) );
		wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
		exit;
	}

	public function handle_export(): void {
		if ( ! current_user_can( RC_Roles::CAP_EXPORT ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_export_ingredients' );

		try {
			$include_sub_recipes = ! empty( $_POST['export_sub_recipes'] );
			RC_Ingredient_Repository::export_csv( $include_sub_recipes );
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_download_template(): void {
		if ( ! current_user_can( RC_Roles::CAP_EXPORT ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_download_template' );

		$path = RC_PLUGIN_DIR . 'data/foodmaster_TMP.csv';
		if ( ! is_readable( $path ) ) {
			RC_Notice::error( __( 'Template file not found.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-settings' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		header( 'Content-Type: text/csv; charset=utf-8' );
		header( 'Content-Disposition: attachment; filename=foodmaster_template.csv' );
		header( 'Content-Length: ' . filesize( $path ) );
		readfile( $path );
		exit;
	}

	public function handle_export_suppliers(): void {
		if ( ! current_user_can( RC_Roles::CAP_EXPORT ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-suppliers' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_export_suppliers' );

		try {
			RC_Supplier_Repository::export_csv();
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-suppliers' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_save_supplier(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SUPPLIERS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-suppliers' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		check_admin_referer( 'rc_save_supplier' );

		try {
			$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;

			$data = array(
				'business_name' => $_POST['business_name'] ?? '',
				'address'       => $_POST['address'] ?? '',
				'phone'         => $_POST['phone'] ?? '',
				'email'         => $_POST['email'] ?? '',
				'contact_name'  => $_POST['contact_name'] ?? '',
				'email_order'   => ! empty( $_POST['email_order'] ),
			);

			$saved_id = RC_Supplier_Repository::upsert( $data, $id > 0 ? $id : null );

			RC_Notice::success( __( 'Supplier saved.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-suppliers' ), admin_url( 'admin.php' ) ) );
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-suppliers' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function handle_delete_supplier(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SUPPLIERS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-suppliers' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		check_admin_referer( 'rc_delete_supplier_' . $id );

		try {
			if ( $id > 0 ) {
				RC_Supplier_Repository::delete( $id );
			}

			RC_Notice::success( __( 'Supplier deleted.', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-suppliers' ), admin_url( 'admin.php' ) ) );
			exit;
		} catch ( RC_Exception $e ) {
			RC_Notice::error( $e->getMessage() );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-suppliers' ), admin_url( 'admin.php' ) ) );
			exit;
		}
	}

	public function ajax_get_supplier(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SUPPLIERS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
			if ( $id === 0 ) {
				wp_send_json_error( array( 'message' => __( 'Invalid supplier ID.', 'restaurant-costing' ) ) );
			}

			$supplier = RC_Supplier_Repository::get( $id );
			if ( ! $supplier ) {
				wp_send_json_error( array( 'message' => __( 'Supplier not found.', 'restaurant-costing' ) ) );
			}

			wp_send_json_success( $supplier );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_save_supplier(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SUPPLIERS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$id   = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
			$data = array(
				'business_name' => sanitize_text_field( wp_unslash( (string) ( $_POST['business_name'] ?? '' ) ) ),
				'address'       => isset( $_POST['address'] ) ? sanitize_textarea_field( wp_unslash( (string) $_POST['address'] ) ) : null,
				'phone'         => sanitize_text_field( wp_unslash( (string) ( $_POST['phone'] ?? '' ) ) ),
				'email'         => sanitize_email( wp_unslash( (string) ( $_POST['email'] ?? '' ) ) ),
				'contact_name'  => sanitize_text_field( wp_unslash( (string) ( $_POST['contact_name'] ?? '' ) ) ),
				'email_order'   => ! empty( $_POST['email_order'] ),
			);

			if ( '' === $data['business_name'] ) {
				wp_send_json_error( array( 'message' => __( 'Supplier name is required.', 'restaurant-costing' ) ) );
			}

			$saved_id = RC_Supplier_Repository::upsert( $data, $id > 0 ? $id : null );
			wp_send_json_success( array( 'id' => $saved_id ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_add_supplier(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SUPPLIERS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['name'] ) ) : '';
			if ( '' === $name ) {
				wp_send_json_error( array( 'message' => __( 'Name is required.', 'restaurant-costing' ) ) );
			}

			$id = RC_Supplier_Repository::find_or_create( $name );
			wp_send_json_success( array( 'id' => $id, 'name' => $name ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_add_area(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_AREAS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['name'] ) ) : '';
			if ( '' === $name ) {
				wp_send_json_error( array( 'message' => __( 'Name is required.', 'restaurant-costing' ) ) );
			}

			$id = RC_Area_Repository::find_or_create( $name );
			wp_send_json_success( array( 'id' => $id, 'name' => $name ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_add_menu_type(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['name'] ) ) : '';
			if ( '' === $name ) {
				wp_send_json_error( array( 'message' => __( 'Name is required.', 'restaurant-costing' ) ) );
			}
			$id = RC_Menu_Type_Repository::find_or_create( $name );
			wp_send_json_success( array( 'id' => $id, 'name' => $name ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_delete_menu_type(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
			if ( $id === 0 ) {
				wp_send_json_error( array( 'message' => __( 'Invalid ID.', 'restaurant-costing' ) ) );
			}
			RC_Menu_Type_Repository::delete( $id );
			wp_send_json_success( array( 'message' => __( 'Menu type deleted.', 'restaurant-costing' ) ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_get_restaurant_info(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$info = RC_Restaurant_Repository::get();
			wp_send_json_success( $info ?: new stdClass() );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_save_restaurant_info(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$data = array(
				'restaurant_name' => isset( $_POST['restaurant_name'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['restaurant_name'] ) ) : '',
				'address'         => isset( $_POST['address'] ) ? sanitize_textarea_field( wp_unslash( (string) $_POST['address'] ) ) : '',
				'phone'           => isset( $_POST['phone'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['phone'] ) ) : '',
				'logo_url'        => isset( $_POST['logo_url'] ) ? esc_url_raw( wp_unslash( (string) $_POST['logo_url'] ) ) : '',
				'sender_name'     => isset( $_POST['sender_name'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['sender_name'] ) ) : '',
				'sender_email'    => isset( $_POST['sender_email'] ) ? sanitize_email( wp_unslash( (string) $_POST['sender_email'] ) ) : '',
			);

			$saved = RC_Restaurant_Repository::save( $data );

			if ( $saved ) {
				wp_send_json_success( array( 'message' => __( 'Restaurant info saved.', 'restaurant-costing' ) ) );
			}

			wp_send_json_error( array( 'message' => __( 'Save failed.', 'restaurant-costing' ) ) );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_upload_restaurant_logo(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_SETTINGS ) ) {
			wp_send_json_error( array( 'message' => 'Unauthorized' ) );
		}

		if ( empty( $_FILES['file'] ) || UPLOAD_ERR_OK !== $_FILES['file']['error'] ) {
			wp_send_json_error( array( 'message' => 'No file uploaded.' ) );
		}

		$file = $_FILES['file'];
		$ext = strtolower( pathinfo( (string) $file['name'], PATHINFO_EXTENSION ) );
		$allowed = array( 'jpg', 'jpeg', 'png', 'gif', 'webp' );

		if ( ! in_array( $ext, $allowed, true ) ) {
			wp_send_json_error( array( 'message' => 'Invalid file type. Allowed: jpg, png, gif, webp.' ) );
		}

		$upload = wp_handle_upload( $file, array( 'test_form' => false ) );

		if ( ! empty( $upload['error'] ) ) {
			wp_send_json_error( array( 'message' => $upload['error'] ) );
		}

		wp_send_json_success( array( 'url' => $upload['url'] ) );
	}

	public function handle_delete_user(): void {
		if ( ! current_user_can( RC_Roles::CAP_MANAGE_USERS ) ) {
			RC_Notice::error( __( 'Unauthorized', 'restaurant-costing' ) );
			wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-users' ), admin_url( 'admin.php' ) ) );
			exit;
		}

		$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		check_admin_referer( 'rc_delete_user_' . $id );

		if ( $id > 0 && $id !== get_current_user_id() ) {
			$user = get_userdata( $id );
			if ( $user && ! empty( array_intersect( array( RC_Roles::ROLE_USER, RC_Roles::ROLE_MANAGER ), (array) $user->roles ) ) ) {
				wp_delete_user( $id );
			}
		}

		RC_Notice::success( __( 'User deleted.', 'restaurant-costing' ) );
		wp_safe_redirect( add_query_arg( array( 'page' => 'restaurant-costing-users' ), admin_url( 'admin.php' ) ) );
		exit;
	}

	public function ajax_get_user(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_USERS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		try {
			$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
			if ( $id === 0 ) {
				wp_send_json_error( array( 'message' => __( 'Invalid user ID.', 'restaurant-costing' ) ) );
			}

			$user = get_userdata( $id );
			if ( ! $user ) {
				wp_send_json_error( array( 'message' => __( 'User not found.', 'restaurant-costing' ) ) );
			}

			$rc_roles = array( RC_Roles::ROLE_USER, RC_Roles::ROLE_MANAGER );
			if ( empty( array_intersect( $rc_roles, (array) $user->roles ) ) ) {
				wp_send_json_error( array( 'message' => __( 'Not an RC user.', 'restaurant-costing' ) ) );
			}

			$data = array(
				'ID'         => $user->ID,
				'user_login' => $user->user_login,
				'user_email' => $user->user_email,
				'first_name' => $user->first_name,
				'last_name'  => $user->last_name,
				'phone'      => get_user_meta( $user->ID, 'phone_number', true ),
				'role'       => ! empty( $user->roles ) ? reset( $user->roles ) : '',
			);

			wp_send_json_success( $data );
		} catch ( RC_Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
	}

	public function ajax_save_user(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_USERS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		$id       = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
		$username = isset( $_POST['username'] ) ? sanitize_user( (string) $_POST['username'] ) : '';
		$email    = isset( $_POST['email'] ) ? sanitize_email( (string) $_POST['email'] ) : '';
		$password = isset( $_POST['password'] ) ? (string) $_POST['password'] : '';
		$role     = isset( $_POST['role'] ) ? sanitize_text_field( (string) $_POST['role'] ) : '';
		$first    = isset( $_POST['first_name'] ) ? sanitize_text_field( (string) $_POST['first_name'] ) : '';
		$last     = isset( $_POST['last_name'] ) ? sanitize_text_field( (string) $_POST['last_name'] ) : '';
		$phone    = isset( $_POST['phone'] ) ? sanitize_text_field( (string) $_POST['phone'] ) : '';

		if ( '' === $username || '' === $email ) {
			wp_send_json_error( array( 'message' => __( 'Username and email are required.', 'restaurant-costing' ) ) );
		}

		if ( ! in_array( $role, array( RC_Roles::ROLE_USER, RC_Roles::ROLE_MANAGER ), true ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid role selected.', 'restaurant-costing' ) ) );
		}

		if ( $id > 0 ) {
			$existing = get_userdata( $id );
			if ( ! $existing ) {
				wp_send_json_error( array( 'message' => __( 'User not found.', 'restaurant-costing' ) ) );
			}

			$userdata = array(
				'ID'         => $id,
				'user_email' => $email,
				'first_name' => $first,
				'last_name'  => $last,
				'role'       => $role,
			);

			if ( '' !== $password ) {
				$userdata['user_pass'] = $password;
			}

			$result = wp_update_user( $userdata );

			if ( is_wp_error( $result ) ) {
				wp_send_json_error( array( 'message' => $result->get_error_message() ) );
			}
		} else {
			if ( '' === $password ) {
				wp_send_json_error( array( 'message' => __( 'Password is required for new users.', 'restaurant-costing' ) ) );
			}

			if ( username_exists( $username ) ) {
				wp_send_json_error( array( 'message' => __( 'Username already exists.', 'restaurant-costing' ) ) );
			}

			if ( email_exists( $email ) ) {
				wp_send_json_error( array( 'message' => __( 'Email already exists.', 'restaurant-costing' ) ) );
			}

			$userdata = array(
				'user_login'   => $username,
				'user_email'   => $email,
				'user_pass'    => $password,
				'first_name'   => $first,
				'last_name'    => $last,
				'role'         => $role,
			);

			$result = wp_insert_user( $userdata );

			if ( is_wp_error( $result ) ) {
				wp_send_json_error( array( 'message' => $result->get_error_message() ) );
			}

			$id = $result;
		}

		if ( '' !== $phone ) {
			update_user_meta( $id, 'phone_number', $phone );
		} else {
			delete_user_meta( $id, 'phone_number' );
		}

		wp_send_json_success( array( 'message' => __( 'User saved.', 'restaurant-costing' ) ) );
	}

	public function ajax_reset_password(): void {
		check_ajax_referer( 'rc_recipe_ajax', 'nonce' );

		if ( ! current_user_can( RC_Roles::CAP_MANAGE_USERS ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized', 'restaurant-costing' ) ) );
		}

		$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
		if ( $id === 0 ) {
			wp_send_json_error( array( 'message' => __( 'Invalid user ID.', 'restaurant-costing' ) ) );
		}

		$user = get_userdata( $id );
		if ( ! $user ) {
			wp_send_json_error( array( 'message' => __( 'User not found.', 'restaurant-costing' ) ) );
		}

		if ( empty( $user->user_email ) ) {
			wp_send_json_error( array( 'message' => __( 'Email is required for this function.', 'restaurant-costing' ) ) );
		}

		$result = retrieve_password( $user->user_login );
		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		wp_send_json_success( array( 'message' => __( 'Password reset email sent.', 'restaurant-costing' ) ) );
	}

	public function register_dashboard_widget(): void {
		wp_add_dashboard_widget(
			'rc_dashboard_widget',
			__( 'Restaurant Costing', 'restaurant-costing' ),
			array( $this, 'render_dashboard_widget' )
		);
	}

	public function render_dashboard_widget(): void {
		if ( ! current_user_can( 'read' ) ) {
			return;
		}
		$recipe_count     = RC_Recipe_Repository::count();
		$ingredient_count = RC_Ingredient_Repository::count();
		$inventory_count  = RC_Inventory_Repository::count();
		$supplier_count   = RC_Supplier_Repository::count();
		$area_count       = count( RC_Area_Repository::get_all() );
		?>
		<div class="rc-widget-stats">
			<div class="rc-widget-stat">
				<strong><?php echo (int) $recipe_count; ?></strong>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=restaurant-costing-recipes' ) ); ?>"><?php esc_html_e( 'Recipes', 'restaurant-costing' ); ?></a>
			</div>
			<div class="rc-widget-stat">
				<strong><?php echo (int) $ingredient_count; ?></strong>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=restaurant-costing-ingredients' ) ); ?>"><?php esc_html_e( 'Ingredients', 'restaurant-costing' ); ?></a>
			</div>
			<div class="rc-widget-stat">
				<strong><?php echo (int) $inventory_count; ?></strong>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=restaurant-costing-inventory' ) ); ?>"><?php esc_html_e( 'Inventories', 'restaurant-costing' ); ?></a>
			</div>
			<div class="rc-widget-stat">
				<strong><?php echo (int) $supplier_count; ?></strong>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=restaurant-costing-suppliers' ) ); ?>"><?php esc_html_e( 'Suppliers', 'restaurant-costing' ); ?></a>
			</div>
			<div class="rc-widget-stat">
				<strong><?php echo (int) $area_count; ?></strong>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=restaurant-costing-areas' ) ); ?>"><?php esc_html_e( 'Areas', 'restaurant-costing' ); ?></a>
			</div>
		</div>
		<?php
	}

	public function render_hal_footer(): void {
		$screen = get_current_screen();
		if ( ! $screen || ! str_contains( $screen->id, 'restaurant-costing' ) ) {
			return;
		}
		?>
		<p style="text-align:center;font-size:11px;color:#6c757d;margin-top:24px;padding:12px 0;border-top:1px solid #273548;">
			Built by <strong>HAL</strong>. Guided by <strong>Steve</strong>. Fueled by fire. | <a href="https://itoversight.ca" style="color:#6c757d;text-decoration:none;">IT Oversight</a> &copy; 2026
		</p>
		<button id="rc-back-to-top" aria-label="Back to top">&#8593;</button>
		<script>
		(function(){
			var btn = document.getElementById('rc-back-to-top');
			if (!btn) return;
			var checkScroll = function(){
				btn.classList.toggle('rc-visible', window.scrollY > 400);
			};
			btn.addEventListener('click', function(){
				window.scrollTo({top: 0, behavior: 'smooth'});
			});
			window.addEventListener('scroll', checkScroll);
			checkScroll();
		})();
		</script>
		<?php if ( get_option( 'rc_show_sal', 1 ) ) : ?>
		<div id="rc-sal" style="pointer-events:none;position:fixed;z-index:999999;font-size:14px;opacity:0;transition:opacity 0.6s;transform:translate(-50%,-50%);">🦎</div>
		<script>
		(function(){
			var sal = document.getElementById('rc-sal');
			if (!sal) return;
			var tx = 0, ty = 0, cx = 0, cy = 0, seen = false;
			document.addEventListener('mousemove', function(e){
				tx = e.clientX; ty = e.clientY;
				if (!seen) { seen = true; sal.style.opacity = '0.5'; }
			});
			function animate(){
				cx += (tx - cx) * 0.04;
				cy += (ty - cy) * 0.04;
				sal.style.left = cx + 'px';
				sal.style.top = cy + 'px';
				requestAnimationFrame(animate);
			}
			cx = tx = window.innerWidth / 2;
			cy = ty = window.innerHeight / 2;
			animate();
		})();
		</script>
		<?php endif; ?>
		<?php
	}
}
