<?php

/**
 * Storzen — Modules Admin Page
 *
 * Renders the toggle-card module management UI, similar to StoreBuild.
 * Called from admin_menu hook. Enqueues its own lightweight CSS + JS.
 *
 * @package storzen
 * @since   1.1.0
 */

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

if (! class_exists('Storzen_Modules_Admin_Page')) {

	class Storzen_Modules_Admin_Page
	{

		/** Admin page slug */
		const SLUG = 'storzen-modules';

		public static function init()
		{
			// Priority 11: Panel::_wp_menus() runs at priority 10, so Modules
			// submenu is added right after Cart, placing it last in the top nav.
			add_action('admin_menu', array(__CLASS__, 'register_page'), 11);
			add_action('admin_enqueue_scripts', array(__CLASS__, 'enqueue_assets'));
		}

		public static function register_page()
		{
			add_submenu_page(
				'storzen',                                       // Parent: Storzen main menu.
				__('Modules', 'storzen'),                     // Page title.
				__('Modules', 'storzen'),                     // Menu label shown in top nav.
				'manage_options',
				self::SLUG,
				array(__CLASS__, 'render')
			);
		}

		public static function enqueue_assets($hook)
		{
			if (strpos($hook, self::SLUG) === false) {
				return;
			}
			// sz-admin CSS/JS is already enqueued by Panel::_assets() because the hook
			// string 'storzen-modules' contains the panel slug 'storzen'.
			// We only need to add our modules-specific inline CSS.
			add_action('admin_head', array(__CLASS__, 'inline_css'));
		}

		public static function inline_css()
		{
?>
			<style id="storzen-modules-css">
				.storzen-modules-wrap * {
					box-sizing: border-box
				}

				.storzen-modules-wrap {
					max-width: 1180px;
					margin: 20px auto 40px;
					font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif
				}

				/* Header bar */
				.sz-mod-topbar {
					background: #fff;
					border: 1px solid #e2e4e7;
					border-radius: 8px;
					padding: 16px 22px;
					display: flex;
					align-items: center;
					justify-content: space-between;
					margin-bottom: 22px
				}

				.sz-mod-topbar h3 {
					font-size: 15px;
					font-weight: 700;
					color: #1d2327;
					margin: 0 0 3px
				}

				.sz-mod-topbar p {
					font-size: 13px;
					color: #6c737a;
					margin: 0
				}

				.sz-mod-save-btn {
					background: #FF3636;
					color: #fff;
					border: none;
					padding: 10px 22px;
					border-radius: 6px;
					font-size: 13px;
					font-weight: 600;
					cursor: pointer;
					transition: background .2s;
					line-height: 1.4
				}

				.sz-mod-save-btn:hover {
					background: #E62E2E
				}

				.sz-mod-save-btn:disabled {
					opacity: .7;
					cursor: not-allowed
				}

				/* Section heading */
				.sz-mod-section {
					margin-bottom: 28px
				}

				.sz-mod-section-title {
					font-size: 13px;
					font-weight: 700;
					color: #1d2327;
					margin-bottom: 14px;
					display: flex;
					align-items: center;
					gap: 10px;
					letter-spacing: .3px
				}

				.sz-mod-section-title::after {
					content: '';
					flex: 1;
					height: 1px;
					background: #e2e4e7
				}

				/* 4-column grid */
				.sz-mod-grid {
					display: grid;
					grid-template-columns: repeat(4, 1fr);
					gap: 12px
				}

				@media(max-width:1100px) {
					.sz-mod-grid {
						grid-template-columns: repeat(3, 1fr)
					}
				}

				@media(max-width:800px) {
					.sz-mod-grid {
						grid-template-columns: repeat(2, 1fr)
					}
				}

				/* Card */
				.sz-mod-card {
					background: #fff;
					border: 1px solid #e2e4e7;
					border-radius: 8px;
					padding: 16px 16px 14px;
					position: relative;
					transition: border-color .15s, box-shadow .15s;
					overflow: hidden
				}

				.sz-mod-card:not(.sz-mod-card--pro):hover {
					border-color: #FFC2C2;
					box-shadow: 0 2px 8px rgba(124, 58, 237, .07)
				}

				.sz-mod-card.sz-is-active {
					border-color: #FF3636
				}

				.sz-mod-card--pro {
					opacity: .72
				}

				.sz-mod-card-top {
					display: flex;
					align-items: flex-start;
					justify-content: space-between;
					gap: 8px
				}

				.sz-mod-card-title {
					font-size: 13px;
					font-weight: 600;
					color: #1d2327;
					flex: 1;
					line-height: 1.45;
					display: flex;
					align-items: flex-start;
					gap: 5px
				}

				.sz-info-icon {
					width: 16px;
					height: 16px;
					border-radius: 50%;
					border: 1.5px solid #9ca3af;
					display: inline-flex;
					align-items: center;
					justify-content: center;
					font-size: 9px;
					color: #9ca3af;
					cursor: pointer;
					flex-shrink: 0;
					margin-top: 1px;
					text-decoration: none
				}

				.sz-info-icon:hover {
					border-color: #FF3636;
					color: #FF3636
				}

				.sz-mod-card-actions {
					display: flex;
					align-items: center;
					gap: 8px;
					flex-shrink: 0;
					margin-top: -2px
				}

				/* Gear btn */
				.sz-gear-btn {
					width: 28px;
					height: 28px;
					border-radius: 6px;
					border: 1px solid #e2e4e7;
					background: #f9fafb;
					display: flex;
					align-items: center;
					justify-content: center;
					cursor: pointer;
					color: #6c737a;
					transition: all .15s;
					padding: 0
				}

				.sz-gear-btn:hover {
					border-color: #FF3636;
					color: #FF3636;
					background: #FFF1F1
				}

				.sz-gear-btn svg {
					width: 14px;
					height: 14px;
					display: block
				}

				/* Toggle */
				.sz-toggle {
					position: relative;
					width: 40px;
					height: 22px;
					flex-shrink: 0;
					display: inline-block
				}

				.sz-toggle input {
					position: absolute;
					opacity: 0;
					width: 0;
					height: 0
				}

				.sz-toggle-track {
					position: absolute;
					inset: 0;
					background: #d1d5db;
					border-radius: 11px;
					transition: background .2s;
					cursor: pointer
				}

				.sz-toggle input:checked+.sz-toggle-track {
					background: #FF3636
				}

				.sz-toggle-track::after {
					content: '';
					position: absolute;
					width: 16px;
					height: 16px;
					border-radius: 50%;
					background: #fff;
					top: 3px;
					left: 3px;
					transition: transform .2s;
					box-shadow: 0 1px 3px rgba(0, 0, 0, .2)
				}

				.sz-toggle input:checked+.sz-toggle-track::after {
					transform: translateX(18px)
				}

				.sz-toggle input:disabled+.sz-toggle-track {
					cursor: not-allowed;
					opacity: .5
				}

				/* PRO ribbon */
				.sz-pro-ribbon {
					position: absolute;
					top: 0;
					left: 0;
					width: 44px;
					height: 44px;
					overflow: hidden;
					pointer-events: none
				}

				.sz-pro-ribbon-label {
					position: absolute;
					top: 7px;
					left: -14px;
					background: linear-gradient(135deg, #FF3636, #FF6B6B);
					color: #fff;
					font-size: 8px;
					font-weight: 800;
					letter-spacing: .5px;
					padding: 2px 18px;
					transform: rotate(-45deg);
					transform-origin: center center;
					line-height: 1.8;
					box-shadow: 0 2px 6px rgba(124, 58, 237, .35)
				}

				/* ── Tabbed Settings Modal ──────────────────────────────────────── */
				.sz-mod-modal {
					position: fixed;
					inset: 0;
					z-index: 100000;
					display: none;
					align-items: center;
					justify-content: center;
					padding: 20px;
					box-sizing: border-box;
					font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif
				}

				.sz-mod-modal.sz-is-open {
					display: flex
				}

				.sz-mod-modal * {
					box-sizing: border-box
				}

				.sz-mod-modal__backdrop {
					position: fixed;
					inset: 0;
					background: rgba(15, 23, 42, .55);
					animation: szModFade .18s ease
				}

				.sz-mod-modal__dialog {
					position: relative;
					background: #fff;
					border-radius: 10px;
					width: 100%;
					max-width: 560px;
					max-height: calc(100vh - 40px);
					display: flex;
					flex-direction: column;
					box-shadow: 0 25px 70px rgba(0, 0, 0, .3);
					animation: szModPop .22s cubic-bezier(.4, 0, .2, 1);
					overflow: hidden
				}

				@keyframes szModFade {
					from {
						opacity: 0
					}

					to {
						opacity: 1
					}
				}

				@keyframes szModPop {
					from {
						transform: translateY(12px) scale(.98);
						opacity: 0
					}

					to {
						transform: none;
						opacity: 1
					}
				}

				.sz-mod-modal__header {
					display: flex;
					align-items: center;
					justify-content: space-between;
					padding: 18px 22px;
					border-bottom: 1px solid #e5e7eb;
					flex-shrink: 0
				}

				.sz-mod-modal__header h3 {
					margin: 0;
					font-size: 15px;
					font-weight: 700;
					color: #1d2327;
					line-height: 1.3
				}

				.sz-mod-modal__header p {
					margin: 2px 0 0;
					font-size: 12px;
					color: #6c737a
				}

				.sz-mod-modal__close {
					width: 30px;
					height: 30px;
					border-radius: 6px;
					background: #f3f4f6;
					border: none;
					color: #6b7280;
					cursor: pointer;
					font-size: 18px;
					line-height: 1;
					display: flex;
					align-items: center;
					justify-content: center;
					padding: 0;
					flex-shrink: 0;
					margin-left: 14px;
					transition: background .15s, color .15s
				}

				.sz-mod-modal__close:hover {
					background: #e5e7eb;
					color: #1d2327
				}

				/* Tab strip */
				.sz-mod-modal__tabs {
					display: flex;
					border-bottom: 1px solid #e5e7eb;
					background: #f9fafb;
					flex-shrink: 0;
					overflow-x: auto;
					scrollbar-width: none
				}

				.sz-mod-modal__tabs::-webkit-scrollbar {
					display: none
				}

				.sz-mod-modal__tab {
					background: transparent;
					border: none;
					padding: 11px 18px;
					font-size: 12px;
					font-weight: 600;
					color: #6b7280;
					cursor: pointer;
					border-bottom: 2px solid transparent;
					margin-bottom: -1px;
					white-space: nowrap;
					transition: color .15s, border-color .15s;
					font-family: inherit;
					letter-spacing: .2px;
					display: inline-flex;
					align-items: center;
					gap: 6px
				}

				.sz-mod-modal__tab:hover {
					color: #1d2327
				}

				.sz-mod-modal__tab.sz-is-active {
					color: #FF3636;
					border-bottom-color: #FF3636
				}

				/* Pro tab badge + active state */
				.sz-mod-modal__tab--pro {
					color: #FF3636
				}

				.sz-mod-modal__tab--pro:hover {
					color: #E62E2E
				}

				.sz-mod-modal__tab--pro.sz-is-active {
					color: #FF3636;
					border-bottom-color: #FF3636
				}

				.sz-tab-pro-badge {
					font-size: 9px;
					line-height: 1;
					font-weight: 800;
					letter-spacing: .6px;
					padding: 3px 6px;
					border-radius: 3px;
					background: linear-gradient(135deg, #FF3636 0%, #FF6B6B 100%);
					color: #fff;
					display: inline-block;
					vertical-align: middle;
					box-shadow: 0 1px 2px rgba(124, 58, 237, .25)
				}

				/* Pro upgrade banner inside a locked tab */
				.sz-pro-upgrade {
					display: flex;
					align-items: center;
					gap: 14px;
					background: linear-gradient(135deg, #FFF5F5 0%, #FFE5E5 100%);
					border: 1px solid #FFD6D6;
					border-radius: 10px;
					padding: 14px 16px;
					margin-bottom: 18px
				}

				.sz-pro-upgrade__icon {
					width: 36px;
					height: 36px;
					border-radius: 50%;
					background: #FF3636;
					color: #fff;
					display: flex;
					align-items: center;
					justify-content: center;
					font-size: 18px;
					flex-shrink: 0;
					box-shadow: 0 2px 6px rgba(124, 58, 237, .3)
				}

				.sz-pro-upgrade__body {
					flex: 1;
					min-width: 0
				}

				.sz-pro-upgrade__title {
					font-size: 13px;
					font-weight: 600;
					color: #1f2937;
					line-height: 1.4
				}

				.sz-pro-upgrade__title strong {
					color: #E62E2E
				}

				.sz-pro-upgrade__desc {
					font-size: 12px;
					color: #6b7280;
					margin-top: 2px;
					line-height: 1.45
				}

				.sz-pro-upgrade__btn {
					flex-shrink: 0;
					background: #FF3636;
					color: #fff !important;
					padding: 8px 14px;
					font-size: 12px;
					font-weight: 600;
					border-radius: 6px;
					text-decoration: none;
					letter-spacing: .2px;
					transition: background .15s, transform .15s;
					box-shadow: 0 1px 3px rgba(124, 58, 237, .25);
					white-space: nowrap
				}

				.sz-pro-upgrade__btn:hover {
					background: #E62E2E;
					transform: translateY(-1px)
				}

				.sz-pro-upgrade__btn:focus {
					outline: 2px solid #FF3636;
					outline-offset: 2px
				}

				/* Locked fieldset — visually dim disabled inputs without WP default ugly gray */
				.sz-pro-fields-lock {
					border: 0;
					padding: 0;
					margin: 0;
					min-width: 0;
					opacity: .55;
					pointer-events: none;
					position: relative
				}

				.sz-pro-fields-lock input,
				.sz-pro-fields-lock select,
				.sz-pro-fields-lock textarea {
					cursor: not-allowed
				}

				.sz-pro-fields-lock .sz-toggle-track {
					cursor: not-allowed
				}

				/* Body — scrollable */
				.sz-mod-modal__body {
					padding: 20px 22px;
					overflow-y: auto;
					flex: 1;
					min-height: 120px
				}

				.sz-mod-modal__panel {
					display: none
				}

				.sz-mod-modal__panel.sz-is-active {
					display: block
				}

				/* Field rows */
				.sz-mod-field {
					margin-bottom: 18px
				}

				.sz-mod-field:last-child {
					margin-bottom: 0
				}

				.sz-mod-field>label,
				.sz-mod-field__title {
					display: block;
					font-size: 12px;
					font-weight: 600;
					color: #1d2327;
					margin: 0 0 6px;
					letter-spacing: .1px
				}

				.sz-mod-field__desc {
					margin-top: 5px;
					font-size: 11px;
					color: #6c737a;
					line-height: 1.5
				}

				.sz-mod-field__desc code {
					background: #f3f4f6;
					padding: 1px 5px;
					border-radius: 3px;
					font-size: 11px;
					color: #4b5563
				}

				.sz-mod-field input[type=text],
				.sz-mod-field input[type=number],
				.sz-mod-field select,
				.sz-mod-field textarea {
					width: 100%;
					border: 1px solid #d1d5db;
					border-radius: 6px;
					padding: 8px 11px;
					font-size: 13px;
					color: #1d2327;
					background: #fff;
					outline: none;
					transition: border-color .15s, box-shadow .15s;
					font-family: inherit
				}

				.sz-mod-field textarea {
					resize: vertical;
					min-height: 74px;
					line-height: 1.5
				}

				.sz-mod-field input:focus,
				.sz-mod-field select:focus,
				.sz-mod-field textarea:focus {
					border-color: #FF3636;
					box-shadow: 0 0 0 3px rgba(124, 58, 237, .12)
				}

				/* Multi-select: checkbox grid */
				.sz-mod-field--multi .sz-multi-grid {
					display: grid;
					grid-template-columns: repeat(2, minmax(0, 1fr));
					gap: 6px 10px;
					margin-top: 8px
				}

				.sz-multi-item {
					display: flex;
					align-items: center;
					gap: 8px;
					padding: 7px 10px;
					border: 1px solid #e5e7eb;
					border-radius: 6px;
					background: #fff;
					cursor: pointer;
					font-size: 12px;
					color: #1d2327;
					transition: border-color .15s, background .15s;
					line-height: 1.3
				}

				.sz-multi-item:hover {
					border-color: #FFC2C2;
					background: #FFF1F1
				}

				.sz-multi-item input {
					accent-color: #FF3636;
					width: 14px;
					height: 14px;
					flex-shrink: 0;
					margin: 0;
					cursor: pointer
				}

				.sz-multi-item input:checked+.sz-multi-label {
					color: #E62E2E;
					font-weight: 600
				}

				.sz-multi-item:has(input:checked) {
					border-color: #FF3636;
					background: #FFF1F1
				}

				/* Color picker row */
				.sz-mod-field--color .sz-color-row {
					display: flex;
					gap: 8px;
					align-items: center
				}

				.sz-color-input {
					width: 42px;
					height: 36px;
					padding: 2px;
					border: 1px solid #d1d5db;
					border-radius: 6px;
					background: #fff;
					cursor: pointer;
					flex-shrink: 0
				}

				.sz-color-input::-webkit-color-swatch-wrapper {
					padding: 0;
					border-radius: 4px;
					overflow: hidden
				}

				.sz-color-input::-webkit-color-swatch {
					border: none;
					border-radius: 4px
				}

				.sz-color-input::-moz-color-swatch {
					border: none;
					border-radius: 4px
				}

				.sz-color-text {
					flex: 1;
					font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
					text-transform: uppercase;
					letter-spacing: .5px
				}

				/* Toggle field inside modal — row layout */
				.sz-mod-field--toggle {
					display: flex;
					align-items: center;
					justify-content: space-between;
					gap: 14px;
					padding: 10px 0;
					border-top: 1px solid #f3f4f6;
					margin-bottom: 0
				}

				.sz-mod-field--toggle:first-child {
					border-top: none;
					padding-top: 0
				}

				.sz-mod-field--toggle>div {
					flex: 1;
					min-width: 0
				}

				.sz-mod-field--toggle .sz-mod-field__title {
					margin-bottom: 2px
				}

				.sz-mod-field--toggle .sz-mod-field__desc {
					margin-top: 0
				}

				/* Footer */
				.sz-mod-modal__footer {
					display: flex;
					align-items: center;
					justify-content: flex-end;
					gap: 10px;
					padding: 14px 22px;
					border-top: 1px solid #e5e7eb;
					background: #f9fafb;
					flex-shrink: 0
				}

				.sz-mod-modal__btn {
					border: none;
					border-radius: 6px;
					padding: 9px 18px;
					font-size: 13px;
					font-weight: 600;
					cursor: pointer;
					font-family: inherit;
					line-height: 1.4;
					transition: background .15s, opacity .15s
				}

				.sz-mod-modal__btn--secondary {
					background: #fff;
					color: #374151;
					border: 1px solid #d1d5db
				}

				.sz-mod-modal__btn--secondary:hover {
					background: #f3f4f6
				}

				.sz-mod-modal__btn--primary {
					background: #FF3636;
					color: #fff
				}

				.sz-mod-modal__btn--primary:hover {
					background: #E62E2E
				}

				.sz-mod-modal__btn:disabled {
					opacity: .7;
					cursor: not-allowed
				}

				@media (max-width:600px) {
					.sz-mod-modal {
						padding: 10px
					}

					.sz-mod-modal__dialog {
						max-height: calc(100vh - 20px)
					}

					.sz-mod-modal__tab {
						padding: 11px 14px;
						font-size: 11px
					}
				}

				@media (prefers-reduced-motion:reduce) {

					.sz-mod-modal__backdrop,
					.sz-mod-modal__dialog {
						animation: none
					}
				}

				/* PRO upgrade strip */


				/* Admin notice / toast */
				#sz-mod-toast {
					position: fixed;
					bottom: 24px;
					right: 24px;
					background: #1d2327;
					color: #fff;
					padding: 10px 18px;
					border-radius: 8px;
					font-size: 13px;
					font-weight: 500;
					opacity: 0;
					transform: translateY(8px);
					transition: all .3s;
					pointer-events: none;
					z-index: 99999
				}

				#sz-mod-toast.show {
					opacity: 1;
					transform: translateY(0)
				}

				#sz-mod-toast.success {
					background: #16a34a
				}

				#sz-mod-toast.error {
					background: #dc2626
				}
			</style>
		<?php
		}

		/**
		 * Render the full modules page HTML.
		 */
		/**
		 * Build the top nav HTML matching Renderer::header() for all panel menus.
		 * Modules tab itself is rendered inline in render() as active.
		 *
		 * @return string Escaped HTML string.
		 */
		public static function get_panel_nav_html()
		{
			// Retrieve the panel instance via a stored reference or rebuild from global.
			global $storzen_panel_instance; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
			if (! isset($storzen_panel_instance) || ! ($storzen_panel_instance instanceof Storzen\Panel)) {
				return '';
			}
			$is_pro      = defined('STORZEN_IS_PRO') && 'yes' === STORZEN_IS_PRO;
			$current_page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$html         = '';
			foreach ($storzen_panel_instance->get_menus() as $menu) {
				$url    = admin_url('admin.php?page=' . $menu->get_page_slug());
				$active = ($current_page === $menu->get_page_slug());
				$cls    = 'sz-header__tab' . ($active ? ' sz-header__tab--active' : '');
				$html  .= '<a href="' . esc_url($url) . '" class="' . esc_attr($cls) . '">';
				if ($menu->get_icon()) {
					$html .= '<span class="sz-header__tab-icon">' . esc_html($menu->get_icon()) . '</span>';
				}
				$html .= esc_html($menu->get_title());
				if ($menu->is_pro() && ! $is_pro) {
					$html .= ' <span class="sz-pro-badge" style="font-size:.55rem;padding:1px 4px 2px">PRO</span>';
				}
				$html .= '</a>';
			}

			// Modules tab.
			$active = ('storzen-modules' === $current_page);
			$html  .= '<a href="' . esc_url(admin_url('admin.php?page=storzen-modules')) . '" class="sz-header__tab' . ($active ? ' sz-header__tab--active' : '') . '">'
				. '<span class="sz-header__tab-icon">&#9889;</span>'
				. esc_html__('Modules', 'storzen')
				. '</a>';

			// Import Templates tab — only if the page is registered.
			if (class_exists('Storzen_Elementor_Template_Import') || get_option('storzen_import_active')) {
				$active = ('storzen-import' === $current_page);
				$html  .= '<a href="' . esc_url(admin_url('admin.php?page=storzen-import-templates')) . '" class="sz-header__tab' . ($active ? ' sz-header__tab--active' : '') . '">'
					. '<span class="sz-header__tab-icon">📥</span>'
					. esc_html__('Import Templates', 'storzen')
					. '</a>';
			}

			return $html;
		}

		public static function render()
		{
			if (! current_user_can('manage_options')) {
				return;
			}

			$modules   = Storzen_Modules::instance();
			$defs      = Storzen_Modules::get_module_definitions();
			$is_pro    = $modules->is_pro_active();
			$saved     = $modules->get_saved_states();

			$free_modules = array_filter($defs, function ($d) {
				return ! $d['pro'];
			});
			$pro_modules  = array_filter($defs, function ($d) {
				return $d['pro'];
			});

			// ── Build the same panel nav that Renderer::header() outputs ──────────────
			// We re-read the Panel menus so the top nav is identical on this page.
			$panel_nav_html = self::get_panel_nav_html();
		?>
			<div class="wrap sz-wrap">

				<?php if (! $is_pro) : ?>
					<div class="sz-banner" id="pkBanner">
						<span><?php esc_html_e('✦ Unlock all PRO widgets & modules — more layouts, Ajax filters, and priority support.', 'storzen'); ?></span>
						<a href="https://storzen.webtend.net/pricing/" class="sz-banner__btn" target="_blank" rel="noopener"><?php esc_html_e('Go Pro →', 'storzen'); ?></a>
						<button class="sz-banner__close" onclick="this.closest('.sz-banner').style.display='none'">&#x2715;</button>
					</div>
				<?php endif; ?>

				<!-- Panel top nav -->
				<div class="sz-header">
					<div class="sz-header__brand"><span class="sz-header__dot"></span><?php echo esc_html__('Storzen', 'storzen'); ?></div>
					<nav class="sz-header__nav">
						<?php echo $panel_nav_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped inside get_panel_nav_html() 
						?>
					</nav>
				</div>

				<div class="sz-body">
					<div class="storzen-modules-wrap">

						<!-- Top bar -->
						<div class="sz-mod-topbar">
							<div>
								<h3><?php esc_html_e('Disable / Enable Modules', 'storzen'); ?></h3>
								<p><?php esc_html_e('You can enable or disable all options by one click.', 'storzen'); ?></p>
							</div>
							<button class="sz-mod-save-btn" id="szModSaveBtn" onclick="szSaveModules()">
								<?php esc_html_e('Save Settings', 'storzen'); ?>
							</button>
						</div>

						<!-- Free modules section -->
						<div class="sz-mod-section">
							<div class="sz-mod-section-title">
								<?php esc_html_e('WooCommerce Modules', 'storzen'); ?>
							</div>
							<div class="sz-mod-grid">
								<?php foreach ($free_modules as $key => $def) : ?>
									<?php self::render_card($key, $def, $modules, $saved, false); ?>
								<?php endforeach; ?>
							</div>
						</div>

						<!-- PRO modules section -->
						<div class="sz-mod-section">
							<div class="sz-mod-section-title">
								<?php esc_html_e('PRO Modules', 'storzen'); ?>
							</div>
							<div class="sz-mod-grid">
								<?php foreach ($pro_modules as $key => $def) : ?>
									<?php self::render_card($key, $def, $modules, $saved, ! $is_pro); ?>
								<?php endforeach; ?>
							</div>


						</div>

					</div>

				</div><!-- /.storzen-modules-wrap -->
			</div><!-- /.sz-body -->
			</div><!-- /.sz-wrap -->

			<?php
			// Render one tabbed settings modal per module that has settings.
			// Output them outside the main wrap so z-index / overflow can't trap them.
			foreach ($defs as $mod_key => $mod_def) {
				$is_pro_mod = ! empty($mod_def['pro']);
				$has_sets   = ! empty($mod_def['has_settings']) && ! empty($mod_def['settings']);
				$locked     = $is_pro_mod && ! $is_pro;
				if (! $has_sets || $locked) {
					continue;
				}
				self::render_settings_modal($mod_key, $mod_def, $saved);
			}
			?>

			<div id="sz-mod-toast"></div>

			<script>
				(function() {
					var nonce = <?php echo wp_json_encode(wp_create_nonce('storzen_modules_nonce')); ?>;
					var ajaxUrl = <?php echo wp_json_encode(admin_url('admin-ajax.php')); ?>;
					var I18N = {
						saving: <?php echo wp_json_encode(__('Saving…', 'storzen')); ?>,
						save: <?php echo wp_json_encode(__('Save Settings', 'storzen')); ?>,
						saveCh: <?php echo wp_json_encode(__('Save Changes', 'storzen')); ?>,
						saved: <?php echo wp_json_encode(__('✓ Settings saved!', 'storzen')); ?>,
						errSave: <?php echo wp_json_encode(__('✗ Error saving.', 'storzen')); ?>,
						errNet: <?php echo wp_json_encode(__('✗ Network error.', 'storzen')); ?>
					};

					var lastFocused = null;

					// ── Modal: open / close ──────────────────────────────────────────────
					function openModal(key) {
						var modal = document.getElementById('sz-modal-' + key);
						if (!modal) return;
						lastFocused = document.activeElement;
						modal.classList.add('sz-is-open');
						modal.setAttribute('aria-hidden', 'false');
						document.body.style.overflow = 'hidden';
						// Focus the close button so ESC works immediately for keyboard users.
						var closeBtn = modal.querySelector('.sz-mod-modal__close');
						if (closeBtn) {
							setTimeout(function() {
								closeBtn.focus();
							}, 30);
						}
					}

					function closeModal(modal) {
						if (!modal) return;
						modal.classList.remove('sz-is-open');
						modal.setAttribute('aria-hidden', 'true');
						// If any modal still open, keep body locked; otherwise unlock.
						if (!document.querySelector('.sz-mod-modal.sz-is-open')) {
							document.body.style.overflow = '';
						}
						if (lastFocused && typeof lastFocused.focus === 'function') {
							lastFocused.focus();
							lastFocused = null;
						}
					}

					// Click handlers — gear, backdrop, close, cancel.
					document.addEventListener('click', function(e) {
						var openTrigger = e.target.closest('[data-sz-open-modal]');
						if (openTrigger) {
							e.preventDefault();
							openModal(openTrigger.dataset.szOpenModal);
							return;
						}

						var closeTrigger = e.target.closest('[data-sz-modal-close]');
						if (closeTrigger) {
							e.preventDefault();
							closeModal(closeTrigger.closest('.sz-mod-modal'));
							return;
						}

						var saveTrigger = e.target.closest('[data-sz-modal-save]');
						if (saveTrigger) {
							e.preventDefault();
							szSaveModules(saveTrigger);
							return;
						}

						var tabBtn = e.target.closest('[data-sz-tab]');
						if (tabBtn) {
							e.preventDefault();
							switchTab(tabBtn.dataset.szTabModule, tabBtn.dataset.szTab);
							return;
						}
					});

					// ESC closes the topmost open modal.
					document.addEventListener('keydown', function(e) {
						if (e.key !== 'Escape') return;
						var openModalEl = document.querySelector('.sz-mod-modal.sz-is-open');
						if (openModalEl) {
							e.stopPropagation();
							closeModal(openModalEl);
						}
					});

					// ── Tab switching ────────────────────────────────────────────────────
					function switchTab(moduleKey, tabKey) {
						var tabs = document.querySelectorAll('[data-sz-tab-module="' + moduleKey + '"]');
						tabs.forEach(function(t) {
							var isActive = t.dataset.szTab === tabKey;
							t.classList.toggle('sz-is-active', isActive);
							t.setAttribute('aria-selected', isActive ? 'true' : 'false');
						});
						var panels = document.querySelectorAll('[data-sz-panel-module="' + moduleKey + '"]');
						panels.forEach(function(p) {
							p.classList.toggle('sz-is-active', p.dataset.szPanel === tabKey);
						});
					}

					// ── Card border state ────────────────────────────────────────────────
					window.szCardToggle = function(key, checkbox) {
						var card = document.getElementById('sz-card-' + key);
						if (card) card.classList.toggle('sz-is-active', checkbox.checked);
					};

					// ── Save all toggles + settings via AJAX ─────────────────────────────
					// triggerBtn (optional) — the button that initiated the save, so we
					// can restore its label / disabled state after the request completes.
					window.szSaveModules = function(triggerBtn) {
						var btn = triggerBtn || document.getElementById('szModSaveBtn');
						var origLabel = btn ? btn.textContent : '';
						if (btn) {
							btn.disabled = true;
							btn.textContent = I18N.saving;
						}

						var modules = {};
						var settings = {};

						document.querySelectorAll('.sz-mod-toggle-input').forEach(function(inp) {
							modules[inp.dataset.module] = inp.checked ? '1' : '0';
						});

						// Helper: skip controls inside a locked Pro <fieldset disabled>.
						// el.disabled is true for any input inside a disabled fieldset
						// (native cascading), so this also catches descendants.
						function szIsLocked(el) {
							return !!(el.disabled || (el.closest && el.closest('.sz-pro-fields-lock')));
						}

						// Scalar fields: toggles, text, textarea, number, select, color (single value).
						document.querySelectorAll('[data-module-setting]').forEach(function(el) {
							if (szIsLocked(el)) return;
							var mod = el.dataset.moduleKey;
							var key = el.dataset.moduleKey2;
							if (!mod || !key) return;
							if (!settings[mod]) settings[mod] = {};
							settings[mod][key] = el.type === 'checkbox' ? (el.checked ? '1' : '0') : el.value;
						});

						// Multi-select: collect checked values per (mod, key) group into an array.
						// Pre-seed every multi_select group with an empty array so unchecking all
						// items actually saves an empty selection instead of being skipped.
						document.querySelectorAll('[data-module-setting-group]').forEach(function(group) {
							if (szIsLocked(group)) return;
							var mod = group.dataset.moduleKey;
							var key = group.dataset.moduleKey2;
							if (!mod || !key) return;
							if (!settings[mod]) settings[mod] = {};
							settings[mod][key] = [];
						});
						document.querySelectorAll('[data-module-multi]').forEach(function(el) {
							if (szIsLocked(el)) return;
							if (!el.checked) return;
							var mod = el.dataset.moduleKey;
							var key = el.dataset.moduleKey2;
							if (!mod || !key) return;
							if (!settings[mod]) settings[mod] = {};
							if (!Array.isArray(settings[mod][key])) settings[mod][key] = [];
							settings[mod][key].push(el.value);
						});

						var body = new FormData();
						body.append('action', 'storzen_save_modules');
						body.append('nonce', nonce);
						Object.keys(modules).forEach(function(k) {
							body.append('modules[' + k + ']', modules[k]);
						});
						Object.keys(settings).forEach(function(mk) {
							Object.keys(settings[mk]).forEach(function(sk) {
								var val = settings[mk][sk];
								if (Array.isArray(val)) {
									// Use the [] suffix so PHP receives a true array.
									if (val.length === 0) {
										// Send a sentinel empty string so the key is present in the request.
										body.append('settings[' + mk + '][' + sk + '][]', '');
									} else {
										val.forEach(function(v) {
											body.append('settings[' + mk + '][' + sk + '][]', v);
										});
									}
								} else {
									body.append('settings[' + mk + '][' + sk + ']', val);
								}
							});
						});

						fetch(ajaxUrl, {
								method: 'POST',
								body: body,
								credentials: 'same-origin'
							})
							.then(function(r) {
								return r.json();
							})
							.then(function(res) {
								if (btn) {
									btn.disabled = false;
									btn.textContent = origLabel || I18N.save;
								}
								szToast(res.success ? I18N.saved : I18N.errSave, res.success ? 'success' : 'error');
								// If saved from inside a modal, close it on success.
								if (res.success && triggerBtn) {
									var modal = triggerBtn.closest('.sz-mod-modal');
									if (modal) {
										// Small delay so the toast is visible before the modal animates out.
										setTimeout(function() {
											closeModal(modal);
										}, 250);
									}
								}
							})
							.catch(function() {
								if (btn) {
									btn.disabled = false;
									btn.textContent = origLabel || I18N.save;
								}
								szToast(I18N.errNet, 'error');
							});
					};

					window.szToast = function(msg, type) {
						var t = document.getElementById('sz-mod-toast');
						if (!t) return;
						t.textContent = msg;
						t.className = 'show ' + (type || '');
						setTimeout(function() {
							t.className = '';
						}, 3000);
					};

					// Color picker ↔ hex text input two-way binding.
					document.addEventListener('input', function(e) {
						var t = e.target;
						// Color picker changed → update text input.
						if (t.classList && t.classList.contains('sz-color-input')) {
							var pair = document.querySelector('[data-sz-color-text="' + t.id + '"]');
							if (pair) pair.value = t.value;
							return;
						}
						// Text input changed → update color picker if valid hex.
						if (t.classList && t.classList.contains('sz-color-text')) {
							var targetId = t.getAttribute('data-sz-color-text');
							var picker = targetId ? document.getElementById(targetId) : null;
							if (picker && /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(t.value.trim())) {
								picker.value = t.value.trim();
							}
						}
					});

					// Init card borders based on current toggle state.
					document.querySelectorAll('.sz-mod-toggle-input').forEach(function(inp) {
						szCardToggle(inp.dataset.module, inp);
					});
				}());
			</script>
		<?php
		}

		/**
		 * Render a single module card.
		 *
		 * @param string          $key        Module key
		 * @param array           $def        Module definition
		 * @param Storzen_Modules $modules    Module manager instance
		 * @param array           $saved      Saved options
		 * @param bool            $force_lock Force-lock because PRO not active
		 */
		private static function render_card($key, $def, $modules, $saved, $force_lock)
		{
			$is_enabled  = $modules->is_enabled($key);
			$is_pro_card = ! empty($def['pro']);
			$has_settings = ! empty($def['has_settings']) && ! empty($def['settings']);

			$card_classes = 'sz-mod-card';
			if ($is_pro_card) $card_classes .= ' sz-mod-card--pro';
			if ($is_enabled)  $card_classes .= ' sz-is-active';
		?>
			<div class="<?php echo esc_attr($card_classes); ?>" id="sz-card-<?php echo esc_attr($key); ?>">

				<?php if ($is_pro_card) : ?>
					<div class="sz-pro-ribbon" aria-hidden="true">
						<span class="sz-pro-ribbon-label">PRO</span>
					</div>
				<?php endif; ?>

				<div class="sz-mod-card-top">
					<span class="sz-mod-card-title" style="<?php echo $is_pro_card ? esc_attr('padding-left:14px') : ''; ?>">
						<?php echo esc_html($def['label']); ?>
						<span class="sz-info-icon" title="<?php echo esc_attr($def['description']); ?>" aria-label="<?php echo esc_attr($def['description']); ?>">i</span>
					</span>

					<div class="sz-mod-card-actions">
						<?php if ($has_settings && ! $force_lock) : ?>
							<button
								type="button"
								class="sz-gear-btn"
								data-sz-open-modal="<?php echo esc_attr($key); ?>"
								aria-label="<?php
											/* translators: %s: module name */
											echo esc_attr(sprintf(__('Open %s settings', 'storzen'), $def['label'])); ?>"
								aria-haspopup="dialog">
								<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
									<circle cx="12" cy="12" r="3" />
									<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
								</svg>
							</button>
						<?php endif; ?>

						<label class="sz-toggle" aria-label="<?php
																/* translators: %s: module name */
																echo esc_attr(sprintf(__('Enable %s', 'storzen'), $def['label'])); ?>">
							<input
								type="checkbox"
								class="sz-mod-toggle-input"
								id="sz-mod-<?php echo esc_attr($key); ?>"
								data-module="<?php echo esc_attr($key); ?>"
								<?php checked($is_enabled); ?>
								<?php disabled($force_lock); ?>
								onchange="szCardToggle('<?php echo esc_attr($key); ?>', this)">
							<span class="sz-toggle-track"></span>
						</label>
					</div>
				</div>

			</div>
		<?php
		}

		/**
		 * Render the tabbed settings modal for a single module.
		 *
		 * Outputs one hidden modal per module. The modal contains all the
		 * settings fields with the same data-module-key / data-module-key2
		 * attributes used by the global Save flow — so saving from inside
		 * the modal goes through the same szSaveModules() AJAX call.
		 *
		 * @param string $key   Module key
		 * @param array  $def   Module definition
		 * @param array  $saved Saved options array
		 */
		private static function render_settings_modal($key, $def, $saved)
		{
			// Normalize tab definitions. Each tab can be:
			//   'general' => 'General'                                 // string label (free tab, back-compat)
			//   'pro'     => array( 'label' => 'Pro ✦', 'pro' => true ) // rich array with optional 'pro' flag
			// After this loop, $normalized_tabs[$key] = array( 'label' => string, 'pro' => bool ).
			$raw_tabs = isset($def['tabs']) && is_array($def['tabs']) && ! empty($def['tabs'])
				? $def['tabs']
				: array('settings' => __('Settings', 'storzen'));

			$normalized_tabs = array();
			foreach ($raw_tabs as $tab_key => $tab_value) {
				if (is_array($tab_value)) {
					$normalized_tabs[$tab_key] = array(
						'label' => isset($tab_value['label']) ? (string) $tab_value['label'] : (string) $tab_key,
						'pro'   => ! empty($tab_value['pro']),
					);
				} else {
					$normalized_tabs[$tab_key] = array(
						'label' => (string) $tab_value,
						'pro'   => false,
					);
				}
			}

			$grouped = array();
			foreach ($normalized_tabs as $tab_key => $_meta) {
				$grouped[$tab_key] = array();
			}

			$first_tab_key = key($normalized_tabs);

			foreach ($def['settings'] as $setting_key => $field) {
				$assigned = isset($field['tab']) && isset($grouped[$field['tab']])
					? $field['tab']
					: $first_tab_key;
				$grouped[$assigned][$setting_key] = $field;
			}

			// Drop empty tabs (declared but no fields use them).
			$grouped = array_filter($grouped, function ($fields) {
				return ! empty($fields);
			});
			if (empty($grouped)) {
				return;
			}

			$tab_keys      = array_keys($grouped);
			$show_tab_strip = count($tab_keys) > 1;

			// Is Pro currently active? (Via STORZEN_IS_PRO.)
			$is_pro_active = defined('STORZEN_IS_PRO') && 'yes' === STORZEN_IS_PRO;
		?>
			<div
				class="sz-mod-modal"
				id="sz-modal-<?php echo esc_attr($key); ?>"
				role="dialog"
				aria-modal="true"
				aria-labelledby="sz-modal-title-<?php echo esc_attr($key); ?>"
				aria-hidden="true">
				<div class="sz-mod-modal__backdrop" data-sz-modal-close="1"></div>
				<div class="sz-mod-modal__dialog" role="document">

					<header class="sz-mod-modal__header">
						<div>
							<h3 id="sz-modal-title-<?php echo esc_attr($key); ?>">
								<?php echo esc_html($def['label']); ?>
							</h3>
							<?php if (! empty($def['description'])) : ?>
								<p><?php echo esc_html($def['description']); ?></p>
							<?php endif; ?>
						</div>
						<button
							type="button"
							class="sz-mod-modal__close"
							data-sz-modal-close="1"
							aria-label="<?php esc_attr_e('Close settings', 'storzen'); ?>">&times;</button>
					</header>

					<?php if ($show_tab_strip) : ?>
						<div class="sz-mod-modal__tabs" role="tablist">
							<?php foreach ($tab_keys as $idx => $tab_key) :
								$tab_meta = $normalized_tabs[$tab_key];
								$tab_classes = 'sz-mod-modal__tab';
								if (0 === $idx) {
									$tab_classes .= ' sz-is-active';
								}
								if (! empty($tab_meta['pro'])) {
									$tab_classes .= ' sz-mod-modal__tab--pro';
								}
							?>
								<button
									type="button"
									role="tab"
									class="<?php echo esc_attr($tab_classes); ?>"
									data-sz-tab="<?php echo esc_attr($tab_key); ?>"
									data-sz-tab-module="<?php echo esc_attr($key); ?>"
									aria-selected="<?php echo 0 === $idx ? 'true' : 'false'; ?>"
									aria-controls="sz-tabpanel-<?php echo esc_attr($key . '-' . $tab_key); ?>">
									<?php echo esc_html($tab_meta['label']); ?>
									<?php if (! empty($tab_meta['pro'])) : ?>
										<span class="sz-tab-pro-badge" aria-label="<?php esc_attr_e('Pro feature', 'storzen'); ?>">PRO</span>
									<?php endif; ?>
								</button>
							<?php endforeach; ?>
						</div>
					<?php endif; ?>

					<div class="sz-mod-modal__body">
						<?php foreach ($tab_keys as $idx => $tab_key) :
							$tab_meta    = $normalized_tabs[$tab_key];
							$is_pro_tab  = ! empty($tab_meta['pro']);
							$is_locked   = $is_pro_tab && ! $is_pro_active;
							$panel_class = 'sz-mod-modal__panel';
							if (0 === $idx) {
								$panel_class .= ' sz-is-active';
							}
							if ($is_locked) {
								$panel_class .= ' sz-mod-modal__panel--locked';
							}
						?>
							<div
								class="<?php echo esc_attr($panel_class); ?>"
								id="sz-tabpanel-<?php echo esc_attr($key . '-' . $tab_key); ?>"
								role="tabpanel"
								data-sz-panel="<?php echo esc_attr($tab_key); ?>"
								data-sz-panel-module="<?php echo esc_attr($key); ?>"
								<?php if ($is_pro_tab) : ?>data-sz-pro-tab="1" <?php endif; ?>
								<?php if ($is_locked) : ?>data-sz-locked="1" <?php endif; ?>>
								<?php if ($is_locked) : ?>
									<div class="sz-pro-upgrade" role="status">
										<div class="sz-pro-upgrade__icon" aria-hidden="true">✦</div>
										<div class="sz-pro-upgrade__body">
											<div class="sz-pro-upgrade__title">
												<?php
												/* translators: %s: module label, e.g. "Quick View" */
												printf(esc_html__('Unlock advanced %s features', 'storzen'), '<strong>' . esc_html($def['label']) . '</strong>'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
												?>
											</div>
											<div class="sz-pro-upgrade__desc">
												<?php esc_html_e('These settings are part of Storzen Pro. Upgrade to unlock them.', 'storzen'); ?>
											</div>
										</div>
										<a href="https://storzen.webtend.net/pricing/" target="_blank" rel="noopener noreferrer" class="sz-pro-upgrade__btn">
											<?php esc_html_e('Get Pro →', 'storzen'); ?>
										</a>
									</div>
									<fieldset class="sz-pro-fields-lock" disabled>
									<?php endif; ?>

									<?php foreach ($grouped[$tab_key] as $setting_key => $field) : ?>
										<?php self::render_field($key, $setting_key, $field, $saved, $is_locked); ?>
									<?php endforeach; ?>

									<?php if ($is_locked) : ?>
									</fieldset><!-- /.sz-pro-fields-lock -->
								<?php endif; ?>
							</div>
						<?php endforeach; ?>
					</div>

					<footer class="sz-mod-modal__footer">
						<button type="button" class="sz-mod-modal__btn sz-mod-modal__btn--secondary" data-sz-modal-close="1">
							<?php esc_html_e('Cancel', 'storzen'); ?>
						</button>
						<button type="button" class="sz-mod-modal__btn sz-mod-modal__btn--primary" data-sz-modal-save="1">
							<?php esc_html_e('Save Changes', 'storzen'); ?>
						</button>
					</footer>

				</div>
			</div>
			<?php
		}

		/**
		 * Render a single settings field (toggle / select / number / text / etc).
		 *
		 * When $locked is true the field is inside a <fieldset disabled> wrapper
		 * (set up in render_settings_modal), so every input is natively disabled
		 * without per-input changes here. We still add a marker class for styling
		 * and a data attribute so the JS save loop can skip these defensively.
		 *
		 * @param string $key         Module key
		 * @param string $setting_key Field key
		 * @param array  $field       Field definition
		 * @param array  $saved       Saved options array
		 * @param bool   $locked      Whether this field is in a locked Pro tab
		 */
		private static function render_field($key, $setting_key, $field, $saved, $locked = false)
		{
			$current_val = isset($saved['settings'][$key][$setting_key])
				? $saved['settings'][$key][$setting_key]
				: $field['default'];
			$field_id   = 'sz-setting-' . $key . '-' . $setting_key;
			$desc       = ! empty($field['description']) ? $field['description'] : '';
			$field_type = isset($field['type']) ? $field['type'] : 'text';
			// Used for the wrapper marker class; native disabling is handled by
			// the surrounding <fieldset disabled> in render_settings_modal.
			$lock_class = $locked ? ' sz-mod-field--locked' : '';

			if ('toggle' === $field_type) :
			?>
				<div class="sz-mod-field sz-mod-field--toggle">
					<div>
						<label for="<?php echo esc_attr($field_id); ?>" class="sz-mod-field__title">
							<?php echo esc_html($field['label']); ?>
						</label>
						<?php if ($desc) : ?>
							<div class="sz-mod-field__desc"><?php echo esc_html($desc); ?></div>
						<?php endif; ?>
					</div>
					<label class="sz-toggle" style="width:34px;height:18px">
						<input
							type="checkbox"
							id="<?php echo esc_attr($field_id); ?>"
							data-module-setting="1"
							data-module-key="<?php echo esc_attr($key); ?>"
							data-module-key2="<?php echo esc_attr($setting_key); ?>"
							<?php checked((bool) $current_val); ?>>
						<span class="sz-toggle-track"></span>
					</label>
				</div>
			<?php
			elseif ('select' === $field_type) :
			?>
				<div class="sz-mod-field">
					<label for="<?php echo esc_attr($field_id); ?>"><?php echo esc_html($field['label']); ?></label>
					<select
						id="<?php echo esc_attr($field_id); ?>"
						data-module-setting="1"
						data-module-key="<?php echo esc_attr($key); ?>"
						data-module-key2="<?php echo esc_attr($setting_key); ?>">
						<?php foreach ($field['options'] as $opt_val => $opt_label) : ?>
							<option value="<?php echo esc_attr($opt_val); ?>" <?php selected($current_val, $opt_val); ?>>
								<?php echo esc_html($opt_label); ?>
							</option>
						<?php endforeach; ?>
					</select>
					<?php if ($desc) : ?>
						<div class="sz-mod-field__desc"><?php echo esc_html($desc); ?></div>
					<?php endif; ?>
				</div>
			<?php
			elseif ('multi_select' === $field_type) :
				// Multiple values stored as array. Render as a styled checkbox grid
				// so users can pick several at once without holding modifier keys.
				$selected_vals = is_array($current_val) ? array_map('strval', $current_val) : array();
			?>
				<div class="sz-mod-field sz-mod-field--multi">
					<span class="sz-mod-field__title"><?php echo esc_html($field['label']); ?></span>
					<div
						class="sz-multi-grid"
						role="group"
						aria-labelledby="<?php echo esc_attr($field_id); ?>-label"
						data-module-setting-group="1"
						data-module-key="<?php echo esc_attr($key); ?>"
						data-module-key2="<?php echo esc_attr($setting_key); ?>">
						<?php foreach ($field['options'] as $opt_val => $opt_label) :
							$opt_id     = $field_id . '-' . sanitize_key((string) $opt_val);
							$is_checked = in_array((string) $opt_val, $selected_vals, true);
						?>
							<label class="sz-multi-item" for="<?php echo esc_attr($opt_id); ?>">
								<input
									type="checkbox"
									id="<?php echo esc_attr($opt_id); ?>"
									class="sz-multi-input"
									data-module-multi="1"
									data-module-key="<?php echo esc_attr($key); ?>"
									data-module-key2="<?php echo esc_attr($setting_key); ?>"
									value="<?php echo esc_attr($opt_val); ?>"
									<?php checked($is_checked); ?>>
								<span class="sz-multi-label"><?php echo esc_html($opt_label); ?></span>
							</label>
						<?php endforeach; ?>
					</div>
					<?php if ($desc) : ?>
						<div class="sz-mod-field__desc"><?php echo esc_html($desc); ?></div>
					<?php endif; ?>
				</div>
			<?php
			elseif ('color' === $field_type) :
				$color_val = is_string($current_val) ? $current_val : '';
			?>
				<div class="sz-mod-field sz-mod-field--color">
					<label for="<?php echo esc_attr($field_id); ?>"><?php echo esc_html($field['label']); ?></label>
					<div class="sz-color-row">
						<input
							type="color"
							id="<?php echo esc_attr($field_id); ?>"
							data-module-setting="1"
							data-module-key="<?php echo esc_attr($key); ?>"
							data-module-key2="<?php echo esc_attr($setting_key); ?>"
							value="<?php echo esc_attr($color_val ?: '#000000'); ?>"
							class="sz-color-input"
							aria-label="<?php echo esc_attr($field['label']); ?>">
						<input
							type="text"
							class="sz-color-text"
							value="<?php echo esc_attr($color_val); ?>"
							placeholder="#RRGGBB"
							aria-label="<?php
										/* translators: %s: setting field label */
										echo esc_attr(sprintf(__('%s hex code', 'storzen'), $field['label'])); ?>"
							data-sz-color-text="<?php echo esc_attr($field_id); ?>">
					</div>
					<?php if ($desc) : ?>
						<div class="sz-mod-field__desc"><?php echo esc_html($desc); ?></div>
					<?php endif; ?>
				</div>
			<?php
			elseif ('textarea' === $field_type) :
			?>
				<div class="sz-mod-field">
					<label for="<?php echo esc_attr($field_id); ?>"><?php echo esc_html($field['label']); ?></label>
					<textarea
						id="<?php echo esc_attr($field_id); ?>"
						data-module-setting="1"
						data-module-key="<?php echo esc_attr($key); ?>"
						data-module-key2="<?php echo esc_attr($setting_key); ?>"
						rows="3"><?php echo esc_textarea((string) $current_val); ?></textarea>
					<?php if ($desc) : ?>
						<div class="sz-mod-field__desc"><?php echo esc_html($desc); ?></div>
					<?php endif; ?>
				</div>
			<?php
			elseif ('number' === $field_type) :
				$is_float = ! empty($field['float']);
			?>
				<div class="sz-mod-field">
					<label for="<?php echo esc_attr($field_id); ?>"><?php echo esc_html($field['label']); ?></label>
					<input
						type="number"
						id="<?php echo esc_attr($field_id); ?>"
						data-module-setting="1"
						data-module-key="<?php echo esc_attr($key); ?>"
						data-module-key2="<?php echo esc_attr($setting_key); ?>"
						value="<?php echo esc_attr($current_val); ?>"
						<?php if (isset($field['min'])) : ?>
						min="<?php echo esc_attr($is_float ? (string) floatval($field['min']) : (string) absint($field['min'])); ?>"
						<?php endif; ?>
						<?php if (isset($field['max'])) : ?>
						max="<?php echo esc_attr($is_float ? (string) floatval($field['max']) : (string) absint($field['max'])); ?>"
						<?php endif; ?>
						<?php if ($is_float) : ?>
						step="any"
						<?php endif; ?>>
					<?php if ($desc) : ?>
						<div class="sz-mod-field__desc"><?php echo esc_html($desc); ?></div>
					<?php endif; ?>
				</div>
			<?php
			elseif ('page_select' === $field_type) :
			?>
				<div class="sz-mod-field">
					<label for="<?php echo esc_attr($field_id); ?>"><?php echo esc_html($field['label']); ?></label>
					<?php
					// wp_dropdown_pages is the official, performant way to render a page picker.
					// It runs a cached get_pages() query internally and falls back gracefully
					// when no pages exist. Echoes its own <select> tag.
					wp_dropdown_pages(array(
						'name'              => esc_attr($field_id),
						'id'                => esc_attr($field_id),
						'selected'          => absint($current_val),
						'show_option_none'  => esc_html__('— Select a page —', 'storzen'),
						'option_none_value' => 0,
					));

					// wp_dropdown_pages doesn't accept arbitrary HTML attributes, so we patch
					// the data-* attributes onto the rendered <select> via a small inline script.
					$injector = sprintf(
						'document.getElementById(%s).setAttribute("data-module-setting","1");' .
							'document.getElementById(%s).setAttribute("data-module-key",%s);' .
							'document.getElementById(%s).setAttribute("data-module-key2",%s);',
						wp_json_encode($field_id),
						wp_json_encode($field_id),
						wp_json_encode($key),
						wp_json_encode($field_id),
						wp_json_encode($setting_key)
					);
					?>
					<script>
						(function() {
							<?php echo $injector; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All values JSON-encoded above. 
							?>
						}());
					</script>
					<?php if ($desc) : ?>
						<div class="sz-mod-field__desc"><?php echo esc_html($desc); ?></div>
					<?php endif; ?>
				</div>
			<?php
			else :
				// text (default)
			?>
				<div class="sz-mod-field">
					<label for="<?php echo esc_attr($field_id); ?>"><?php echo esc_html($field['label']); ?></label>
					<input
						type="text"
						id="<?php echo esc_attr($field_id); ?>"
						data-module-setting="1"
						data-module-key="<?php echo esc_attr($key); ?>"
						data-module-key2="<?php echo esc_attr($setting_key); ?>"
						value="<?php echo esc_attr($current_val); ?>">
					<?php if ($desc) : ?>
						<div class="sz-mod-field__desc">
							<?php
							// Allow <code> tags in the description so we can show placeholders like {name}.
							echo wp_kses($desc, array('code' => array()));
							?>
						</div>
					<?php endif; ?>
				</div>
<?php
			endif;
		}
	} // end class

	Storzen_Modules_Admin_Page::init();
} // end class_exists
