<?php
/**
 * Admin UI — Panelra Free.
 *
 * Registers the settings page, settings groups, and renders each tab.
 *
 * @package Panelra
 */

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

/**
 * Class Pnlrdb_Admin
 */
class Pnlrdb_Admin {

	/** Settings page slug. */
	private $page_slug = 'pnlrdb-settings';

	/**
	 * Register hooks.
	 */
	public function __construct() {
		add_action( 'admin_menu',            array( $this, 'add_admin_menu' ) );
		add_action( 'admin_init',            array( $this, 'register_settings' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
	}

	/* ------------------------------------------------------------------
	 * Menu
	 * ---------------------------------------------------------------- */

	/**
	 * Register the top-level admin menu entry.
	 *
	 * @return void
	 */
	public function add_admin_menu() {
		$menu_title = apply_filters( 'pnlrdb_menu_display_title', __( 'Panelra', 'panelra-dashboard-builder' ) );
		$menu_title = apply_filters( 'pnlrdb_menu_display_title', $menu_title );

		add_menu_page(
			__( 'Panelra', 'panelra-dashboard-builder' ),
			$menu_title,
			'manage_options',
			$this->page_slug,
			array( $this, 'render_settings_page' ),
			'data:image/svg+xml;base64,' . base64_encode( '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#a0a0a0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M4.22 4.22l2.12 2.12M17.66 17.66l2.12 2.12M2 12h3M19 12h3M4.22 19.78l2.12-2.12M17.66 6.34l2.12-2.12"/></svg>' ), // phpcs:ignore
			100
		);
	}

	/* ------------------------------------------------------------------
	 * Settings registration
	 * ---------------------------------------------------------------- */

	/**
	 * Maps settings groups to their options.
	 * Each tab uses its own isolated group so saving one tab never touches another.
	 *
	 * @return array
	 */
	private function get_settings_map() {
		return array(
			'pnlrdb_group_colors' => array(
				array( 'pnlrdb_menu_width',            'integer', array( $this, 'sanitize_menu_width' ),       220 ),
				array( 'pnlrdb_dashboard_colors',      'array',   array( $this, 'sanitize_colors' ),           array( 'sidebar_bg' => '#0d003a' ) ),
				array( 'pnlrdb_dashboard_font',        'string',  'sanitize_text_field',                       'Roboto, Arial, sans-serif' ),
				array( 'pnlrdb_dashboard_font_size',   'integer', array( $this, 'sanitize_font_size' ),        15 ),
				array( 'pnlrdb_dashboard_font_color',  'string',  'sanitize_hex_color',                        '#1e293b' ),
				array( 'pnlrdb_dark_mode_global',      'string',  array( $this, 'sanitize_dark_mode_global' ), 'off' ),
				array( 'pnlrdb_button_text_color',     'string',  'sanitize_hex_color',                        '' ),
				array( 'pnlrdb_active_theme_preset',    'string',  'sanitize_key',                              'default' ),
			),
			'pnlrdb_group_dashboard' => array(
				array( 'pnlrdb_hidden_dashboard_widgets', 'array', array( $this, 'sanitize_dashboard_widgets' ), array() ),
			),
			'pnlrdb_group_menu' => array(
				array( 'pnlrdb_menu_names', 'array', array( $this, 'sanitize_kv_array' ), array() ),
			),
			'pnlrdb_group_adminbar' => array(
				array( 'pnlrdb_hide_admin_bar_roles', 'array', array( $this, 'sanitize_str_array' ), array() ),
			),
			'pnlrdb_group_notices' => array(
				array( 'pnlrdb_hide_notices', 'integer', array( $this, 'sanitize_checkbox' ), 0 ),
			),
			'pnlrdb_group_roles' => array(
				array( 'pnlrdb_role_menu_visibility', 'array', array( $this, 'sanitize_role_vis' ), array() ),
			),
			'pnlrdb_group_maintenance' => array(
				array( 'pnlrdb_maintenance_enabled', 'integer', array( $this, 'sanitize_checkbox' ),   0 ),
				array( 'pnlrdb_maintenance_title',   'string',  'sanitize_text_field',                 '' ),
				array( 'pnlrdb_maintenance_message', 'string',  'sanitize_textarea_field',             '' ),
			),
		);
	}

	/**
	 * Returns the settings group that corresponds to a given tab slug.
	 *
	 * @param  string $tab Tab slug.
	 * @return string      Settings group name.
	 */
	public function get_group_for_tab( $tab ) {
		$tab_groups = array(
			'colors'      => 'pnlrdb_group_colors',
			'menu'        => 'pnlrdb_group_menu',
			'adminbar'    => 'pnlrdb_group_adminbar',
			'notices'     => 'pnlrdb_group_notices',
			'roles'       => 'pnlrdb_group_roles',
			'maintenance' => 'pnlrdb_group_maintenance',
		);

		$group = apply_filters( 'pnlrdb_group_for_tab', $tab_groups[ $tab ] ?? '', $tab );
		return apply_filters( 'pnlrdb_group_for_tab', $group, $tab );
	}

	/**
	 * Register all settings via the WordPress Settings API.
	 *
	 * @return void
	 */
	public function register_settings() {
		foreach ( $this->get_settings_map() as $group => $settings ) {
			foreach ( $settings as $s ) {
				register_setting(
					$group,
					$s[0],
					array(
						'type'              => $s[1],
						'sanitize_callback' => $s[2],
						'default'           => $s[3],
					)
				);
			}
		}
	}

	/* ------------------------------------------------------------------
	 * Assets
	 * ---------------------------------------------------------------- */

	/**
	 * Enqueue scripts and styles.
	 *
	 * @param string $hook Current admin page hook.
	 * @return void
	 */
	public function enqueue_assets( $hook ) {
		// Sidebar accordion JS — enqueued on all admin pages.
		wp_enqueue_script(
			'pnlrdb-js',
			PNLRDB_PLUGIN_URL . 'admin/js/pnlrdb-admin.js',
			array( 'jquery' ),
			PNLRDB_VERSION,
			true
		);
		wp_localize_script(
			'pnlrdb-js',
			'pnlrdb',
			array(
				'ajax_url' => admin_url( 'admin-ajax.php' ),
				'nonce'    => wp_create_nonce( 'pnlrdb_nonce' ),
			)
		);

		// Settings page assets.
		if ( 'toplevel_page_' . $this->page_slug !== $hook ) {
			return;
		}

		// Suppress horizontal scrollbar caused by negative margins on .ap773-page.
		wp_register_style( 'pnlrdb-page-layout', false, array(), PNLRDB_VERSION );
		wp_enqueue_style( 'pnlrdb-page-layout' );
		wp_add_inline_style( 'pnlrdb-page-layout',
			'.toplevel_page_pnlrdb-settings #wpcontent { padding: 0 !important; }' .
			'.toplevel_page_pnlrdb-settings #wpbody-content .wrap { margin: 0 !important; padding: 0 !important; }' .
			'.toplevel_page_pnlrdb-settings #wpbody-content,' .
			'.toplevel_page_pnlrdb-settings #wpbody,' .
			'.toplevel_page_pnlrdb-settings #wpcontent { overflow-x: clip !important; }'
		);

		wp_enqueue_media();
		wp_enqueue_style( 'wp-color-picker' );
		wp_enqueue_script( 'wp-color-picker' );
		wp_enqueue_style(
			'pnlrdb-css',
			PNLRDB_PLUGIN_URL . 'admin/css/pnlrdb-admin.css',
			array(),
			PNLRDB_VERSION
		);

		wp_enqueue_style(
			'pnlrdb-tab-colors-css',
			PNLRDB_PLUGIN_URL . 'admin/css/pnlrdb-tab-colors.css',
			array( 'pnlrdb-css' ),
			PNLRDB_VERSION
		);

		wp_enqueue_style(
			'pnlrdb-tab-dashboard-css',
			PNLRDB_PLUGIN_URL . 'admin/css/pnlrdb-tab-dashboard.css',
			array( 'pnlrdb-css' ),
			PNLRDB_VERSION
		);

		wp_enqueue_script(
			'pnlrdb-tab-colors',
			PNLRDB_PLUGIN_URL . 'admin/js/pnlrdb-tab-colors.js',
			array( 'jquery', 'wp-color-picker', 'pnlrdb-js' ),
			PNLRDB_VERSION,
			true
		);
		wp_localize_script(
			'pnlrdb-tab-colors',
			'pnlrdbColors',
			array(
				'presets' => class_exists( 'Pnlrdb_Theme_Presets' ) ? Pnlrdb_Theme_Presets::get_presets() : array(),
				'i18n'    => array(
					'resetDefault' => __( 'Reset to WordPress default', 'panelra-dashboard-builder' ),
					'applyTheme'   => __( 'Apply this theme', 'panelra-dashboard-builder' ),
					'darkModeOn'   => __( 'Dark mode ON', 'panelra-dashboard-builder' ),
					'lightMode'    => __( 'Light mode', 'panelra-dashboard-builder' ),
				),
			)
		);

		wp_enqueue_script(
			'pnlrdb-builder',
			PNLRDB_PLUGIN_URL . 'admin/js/pnlrdb-builder.js',
			array( 'jquery', 'pnlrdb-js' ),
			PNLRDB_VERSION,
			true
		);
		wp_localize_script(
			'pnlrdb-builder',
			'pnlrdbBuilder',
			array(
				'ajaxUrl'       => admin_url( 'admin-ajax.php' ),
				'nonce'         => class_exists( 'Pnlrdb_Dashboard_Builder' ) ? wp_create_nonce( Pnlrdb_Dashboard_Builder::NONCE_ACTION ) : '',
				'layout'        => class_exists( 'Pnlrdb_Dashboard_Builder' ) ? Pnlrdb_Dashboard_Builder::get_layout() : array(),
				'blockTypes'    => class_exists( 'Pnlrdb_Dashboard_Builder' ) ? Pnlrdb_Dashboard_Builder::get_block_types() : array(),
				'templates'     => class_exists( 'Pnlrdb_Dashboard_Builder' ) ? Pnlrdb_Dashboard_Builder::get_dashboard_templates() : array(),
				'actionChoices' => $this->get_builder_action_choices(),
				'roles'         => array_map( static function( $role ) { return $role['name']; }, get_editable_roles() ),
				'allRolesLabel' => __( 'All roles', 'panelra-dashboard-builder' ),
				'i18n'          => array(
					'saving'          => __( 'Saving…', 'panelra-dashboard-builder' ),
					'saved'           => __( 'Layout saved.', 'panelra-dashboard-builder' ),
					'saveError'       => __( 'Save failed. Please try again.', 'panelra-dashboard-builder' ),
					'confirmDel'      => __( 'Delete this block?', 'panelra-dashboard-builder' ),
					'confirmSecDel'   => __( 'Delete this section and all its blocks?', 'panelra-dashboard-builder' ),
					'blockSettings'   => __( 'Block settings', 'panelra-dashboard-builder' ),
					'sectionSettings' => __( 'Section settings', 'panelra-dashboard-builder' ),
					'visibleToRoles'  => __( 'Visible to roles', 'panelra-dashboard-builder' ),
					'columns'         => __( 'Columns', 'panelra-dashboard-builder' ),
					'section'         => __( 'Section', 'panelra-dashboard-builder' ),
					'column'          => __( 'column', 'panelra-dashboard-builder' ),
					'columnsPlural'   => __( 'columns', 'panelra-dashboard-builder' ),
					'moveUp'          => __( 'Move up', 'panelra-dashboard-builder' ),
					'moveDown'        => __( 'Move down', 'panelra-dashboard-builder' ),
					'deleteSection'   => __( 'Delete section', 'panelra-dashboard-builder' ),
					'deleteBlock'     => __( 'Delete block', 'panelra-dashboard-builder' ),
					'chooseMedia'     => __( 'Choose from Media Library', 'panelra-dashboard-builder' ),
					'showCounters'    => __( 'Show counters', 'panelra-dashboard-builder' ),
					'dividerDesc'     => __( 'A horizontal divider line.', 'panelra-dashboard-builder' ),
					'selectImage'     => __( 'Select image', 'panelra-dashboard-builder' ),
					'useThisImage'    => __( 'Use this image', 'panelra-dashboard-builder' ),
					'defaultHeading'  => __( 'Welcome to your dashboard', 'panelra-dashboard-builder' ),
					'defaultText'     => __( 'Add your text here.', 'panelra-dashboard-builder' ),
					'defaultButton'   => __( 'Click here', 'panelra-dashboard-builder' ),
					'confirmTemplateReplace' => __( 'This will replace your current dashboard layout. Continue?', 'panelra-dashboard-builder' ),
					'templateInstalled' => __( 'Template installed. You can now customize it with the Dashboard Builder.', 'panelra-dashboard-builder' ),
					'choosePreset'   => __( 'Choose preset/page…', 'panelra-dashboard-builder' ),
					'actionEditorHelp' => __( 'Preconfigured buttons are ready to use. Edit the label, icon and URL, choose a site page/content, or enter a manual link.', 'panelra-dashboard-builder' ),
					'addAction'      => __( 'Add action', 'panelra-dashboard-builder' ),
					'newAction'      => __( 'New action', 'panelra-dashboard-builder' ),
					'actions'        => __( 'Actions', 'panelra-dashboard-builder' ),
					'links'          => __( 'Links', 'panelra-dashboard-builder' ),
				),
			)
		);

		wp_enqueue_script(
			'pnlrdb-tab-roles',
			PNLRDB_PLUGIN_URL . 'admin/js/pnlrdb-tab-roles.js',
			array( 'jquery' ),
			PNLRDB_VERSION,
			true
		);
		wp_localize_script(
			'pnlrdb-tab-roles',
			'pnlrdbRoles',
			array(
				'nonce' => wp_create_nonce( 'pnlrdbpro_nonce' ),
				'i18n'  => array(
					'reset'              => __( 'Reset', 'panelra-dashboard-builder' ),
					'resetConfirmSuffix' => __( 'to Client Mode defaults? Your manual changes will be overwritten.', 'panelra-dashboard-builder' ),
					'resetting'          => __( 'Resetting…', 'panelra-dashboard-builder' ),
					'error'              => __( 'Error. Please try again.', 'panelra-dashboard-builder' ),
					'resetToPreset'      => __( 'Reset to preset', 'panelra-dashboard-builder' ),
					'done'               => __( 'Done!', 'panelra-dashboard-builder' ),
				),
			)
		);
	}


	/**
	 * Return predefined actions and site content choices for the Dashboard Builder action editor.
	 *
	 * Kept inside the admin class so the builder page can localize ready-to-use
	 * buttons without changing any saved layouts. These values are only used by
	 * the editor UI; existing dashboards remain untouched until the user saves.
	 *
	 * @return array
	 */
	private function get_builder_action_choices() {
		$choices = array(
			array(
				'label' => __( 'WordPress actions', 'panelra-dashboard-builder' ),
				'items' => array(
					array( 'icon' => '✏️', 'label' => __( 'Add New Post', 'panelra-dashboard-builder' ), 'url' => admin_url( 'post-new.php' ) ),
					array( 'icon' => '📄', 'label' => __( 'Add New Page', 'panelra-dashboard-builder' ), 'url' => admin_url( 'post-new.php?post_type=page' ) ),
					array( 'icon' => '🖼️', 'label' => __( 'Upload Media', 'panelra-dashboard-builder' ), 'url' => admin_url( 'media-new.php' ) ),
					array( 'icon' => '🌐', 'label' => __( 'View Website', 'panelra-dashboard-builder' ), 'url' => home_url( '/' ) ),
					array( 'icon' => '📝', 'label' => __( 'Manage Posts', 'panelra-dashboard-builder' ), 'url' => admin_url( 'edit.php' ) ),
					array( 'icon' => '📑', 'label' => __( 'Edit Pages', 'panelra-dashboard-builder' ), 'url' => admin_url( 'edit.php?post_type=page' ) ),
					array( 'icon' => '🔌', 'label' => __( 'Manage Plugins', 'panelra-dashboard-builder' ), 'url' => admin_url( 'plugins.php' ) ),
					array( 'icon' => '🎨', 'label' => __( 'Customize Theme', 'panelra-dashboard-builder' ), 'url' => admin_url( 'customize.php' ) ),
					array( 'icon' => '👥', 'label' => __( 'Manage Users', 'panelra-dashboard-builder' ), 'url' => admin_url( 'users.php' ) ),
					array( 'icon' => '⚙️', 'label' => __( 'General Settings', 'panelra-dashboard-builder' ), 'url' => admin_url( 'options-general.php' ) ),
				),
			),
		);

		$content_items = array();

		if ( function_exists( 'get_posts' ) ) {
			$posts = get_posts(
				array(
					'post_type'      => array( 'page', 'post' ),
					'post_status'    => 'publish',
					'posts_per_page' => 40,
					'orderby'        => 'date',
					'order'          => 'DESC',
					'no_found_rows'  => true,
				)
			);

			foreach ( $posts as $post ) {
				$content_items[] = array(
					'icon'  => 'page' === $post->post_type ? '📄' : '📝',
					'label' => get_the_title( $post ),
					'url'   => get_permalink( $post ),
				);
			}
		}

		if ( ! empty( $content_items ) ) {
			$choices[] = array(
				'label' => __( 'Site pages and posts', 'panelra-dashboard-builder' ),
				'items' => $content_items,
			);
		}

		/**
		 * Filters the action choices displayed in the Dashboard Builder editor.
		 *
		 * @param array $choices Grouped action choices.
		 */
		return apply_filters( 'pnlrdb_builder_action_choices', $choices );
	}

	/* ------------------------------------------------------------------
	 * Render page
	 * ---------------------------------------------------------------- */

	/**
	 * Render the full settings page (sidebar + main content).
	 *
	 * @return void
	 */
	public function render_settings_page() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'colors'; // phpcs:ignore

		/**
		 * Filters the list of settings tabs.
		 * Pro adds its own tabs via this filter.
		 *
		 * @param array $tabs Associative array: tab_slug => array( label, icon )
		 */
		$tabs = apply_filters(
			'pnlrdb_settings_tabs',
			array(
				'colors'      => array( 'label' => __( 'Appearance', 'panelra-dashboard-builder' ),  'icon' => '🎨' ),
				'menu'        => array( 'label' => __( 'Menu', 'panelra-dashboard-builder' ),         'icon' => '☰' ),
				'adminbar'    => array( 'label' => __( 'Admin Bar', 'panelra-dashboard-builder' ),    'icon' => '↑' ),
				'notices'     => array( 'label' => __( 'Notices', 'panelra-dashboard-builder' ),      'icon' => '🔔' ),
				'roles'       => array( 'label' => __( 'Roles', 'panelra-dashboard-builder' ),        'icon' => '👥' ),
				'maintenance' => array( 'label' => __( 'Maintenance', 'panelra-dashboard-builder' ),  'icon' => '🔧' ),
				'tutorials'   => array( 'label' => __( 'Tutorials', 'panelra-dashboard-builder' ),    'icon' => '🎥' ),
				'dashboard'   => array( 'label' => __( 'Dashboard Builder', 'panelra-dashboard-builder' ), 'icon' => '⬡' ),
				'logs'        => array( 'label' => __( 'Logs', 'panelra-dashboard-builder' ),         'icon' => '📋' ),
			)
		);
		$tabs = apply_filters( 'pnlrdb_settings_tabs', $tabs );

		/**
		 * Filters which tabs are wrapped in a settings form.
		 * Pro tabs that need their own save logic can be excluded here.
		 *
		 * @param array $form_tabs List of tab slugs that use the Settings API form.
		 */
		$form_tabs = apply_filters(
			'pnlrdb_form_tabs',
			array( 'colors', 'menu', 'adminbar', 'notices', 'roles', 'maintenance' )
		);
		$form_tabs = apply_filters( 'pnlrdb_form_tabs', $form_tabs );

		$use_form = in_array( $tab, $form_tabs, true );
		?>
		<div class="ap773-page">

			<!-- Sidebar navigation -->
			<aside class="ap773-sidebar">
				<div class="ap773-logo">
					<svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
						<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2"/>
						<path d="M12 2v3M12 19v3M4.22 4.22l2.12 2.12M17.66 17.66l2.12 2.12M2 12h3M19 12h3M4.22 19.78l2.12-2.12M17.66 6.34l2.12-2.12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
					</svg>
					<span>Panelra</span>
				</div>

				<nav class="ap773-nav">
					<?php foreach ( $tabs as $tab_key => $tab_data ) : ?>
						<a href="<?php echo esc_url( add_query_arg( array( 'page' => $this->page_slug, 'tab' => $tab_key ), admin_url( 'admin.php' ) ) ); ?>"
						   class="ap773-nav-item<?php echo $tab === $tab_key ? ' is-active' : ''; ?>">
							<span class="ap773-nav-icon"><?php echo esc_html( $tab_data['icon'] ); ?></span>
							<span><?php echo esc_html( $tab_data['label'] ); ?></span>
						</a>
					<?php endforeach; ?>
				</nav>

				<!-- Pro upsell banner (visible only when Pro is not active) -->
				<?php if ( ! defined( 'PNLRDBPRO_VERSION' ) ) : ?>
				<div class="ap773-pro-banner">
					<div class="ap773-pro-banner-badge">PRO</div>
					<p class="ap773-pro-banner-title"><?php esc_html_e( 'Unlock more power', 'panelra-dashboard-builder' ); ?></p>
					<p class="ap773-pro-banner-text"><?php esc_html_e( 'Panelra Pro is coming soon with drag & drop menus, white label, client mode, dashboard widgets and more.', 'panelra-dashboard-builder' ); ?></p>
					<span class="ap773-pro-banner-btn ap773-pro-banner-btn-disabled" aria-disabled="true">
						<?php esc_html_e( 'Coming Soon', 'panelra-dashboard-builder' ); ?>
					</span>
				</div>
				<?php endif; ?>

				<div class="ap773-sidebar-footer">
					<span>v<?php echo esc_html( PNLRDB_VERSION ); ?></span>
					<?php if ( defined( 'PNLRDBPRO_VERSION' ) ) : ?>
						<span class="ap773-pro-pill">PRO</span>
					<?php endif; ?>
				</div>
			</aside>

			<!-- Main content -->
			<main class="ap773-main">
				<div class="ap773-main-inner">

					<?php if ( isset( $_GET['settings-updated'] ) ) : // phpcs:ignore ?>
						<div class="ap773-notice ap773-notice--success">
							<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
							<?php esc_html_e( 'Settings saved successfully.', 'panelra-dashboard-builder' ); ?>
						</div>
					<?php endif; ?>

					<?php if ( $use_form ) : ?>
						<form method="post" action="options.php" class="ap773-form">
							<?php settings_fields( $this->get_group_for_tab( $tab ) ); ?>
							<?php $this->render_tab( $tab ); ?>
							<div class="ap773-form-footer">
								<?php submit_button( __( 'Save Settings', 'panelra-dashboard-builder' ), 'primary ap773-btn ap773-btn--primary', 'submit', false ); ?>
							</div>
						</form>
					<?php else : ?>
						<?php $this->render_tab( $tab ); ?>
					<?php endif; ?>

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

	/**
	 * Include the correct partial file for the given tab.
	 *
	 * @param  string $tab Tab slug.
	 * @return void
	 */
	private function render_tab( $tab ) {
		$allowed_tabs = array(
			'colors', 'menu', 'adminbar', 'notices', 'roles',
			'maintenance', 'tutorials', 'dashboard', 'logs',
		);

		if ( in_array( $tab, $allowed_tabs, true ) ) {
			$file = PNLRDB_PLUGIN_DIR . 'admin/partials/tab-' . $tab . '.php';
			if ( file_exists( $file ) ) {
				include $file;
				return;
			}
		}

		/**
		 * Allows Pro (or other extensions) to render custom tabs.
		 *
		 * @param string $tab The tab slug that was not found in Free partials.
		 */
		do_action( 'pnlrdb_render_tab_' . $tab );
		do_action( 'pnlrdb_render_tab_' . $tab );
	}

	/* ------------------------------------------------------------------
	 * Sanitize callbacks
	 * ---------------------------------------------------------------- */

	/**
	 * Sanitize the dashboard widgets option.
	 *
	 * Input from form:
	 *   global_visible[]               — widget IDs that are checked (visible) for global default
	 *   role_enabled[role]             — 1 if override is on for this role
	 *   role_visible[role][]           — widget IDs that are checked (visible) for this role
	 *
	 * Stored in DB:
	 *   global[]                       — widget IDs that are HIDDEN globally
	 *   role_hidden[role][]            — widget IDs that are HIDDEN for this role
	 *   role_enabled[role]             — true if role override is active
	 *
	 * @param  mixed $input Raw POST data.
	 * @return array        Sanitized data.
	 */
	public function sanitize_dashboard_widgets( $input ) {
		if ( ! is_array( $input ) ) {
			return array();
		}

		$out     = array();
		$all_ids = array_keys( Pnlrdb_Dashboard_Manager::get_default_widgets() );
		$roles   = array_keys( get_editable_roles() );

		// --- Global: convert visible list to hidden list ---
		$global_visible = isset( $input['global_visible'] ) ? array_map( 'sanitize_key', (array) $input['global_visible'] ) : array();
		$global_visible = array_intersect( $global_visible, $all_ids );
		// Widgets NOT in the visible list are hidden.
		$out['global'] = array_values( array_diff( $all_ids, $global_visible ) );

		// --- Per-role overrides ---
		$out['role_enabled'] = array();
		$out['role_hidden']  = array();

		foreach ( $roles as $role ) {
			$role = sanitize_key( $role );

			// Is the override toggle checked for this role?
			$enabled = ! empty( $input['role_enabled'][ $role ] );
			if ( $enabled ) {
				$out['role_enabled'][ $role ] = true;
			}

			// Convert role visible list to hidden list.
			$role_visible = isset( $input['role_visible'][ $role ] )
				? array_map( 'sanitize_key', (array) $input['role_visible'][ $role ] )
				: array();
			$role_visible = array_intersect( $role_visible, $all_ids );
			$out['role_hidden'][ $role ] = array_values( array_diff( $all_ids, $role_visible ) );
		}

		return $out;
	}

	/**
	 * Sanitize sidebar menu width (160–500 px).
	 *
	 * @param  mixed $value Raw value.
	 * @return int          Clamped integer.
	 */
	public function sanitize_menu_width( $value ) {
		return max( 160, min( 500, absint( $value ) ) );
	}

	/**
	 * Sanitize font size (10–22 px).
	 *
	 * @param  mixed $value Raw value.
	 * @return int          Clamped integer.
	 */
	public function sanitize_font_size( $value ) {
		return max( 10, min( 22, absint( $value ) ) );
	}

	/**
	 * Sanitize the dashboard colors array.
	 *
	 * @param  mixed $input Raw input.
	 * @return array        Sanitized array with 'primary' and 'sidebar_bg'.
	 */
	public function sanitize_colors( $input ) {
		if ( ! is_array( $input ) ) {
			return array();
		}
		$out = array();
		if ( isset( $input['primary'] ) ) {
			$out['primary'] = sanitize_hex_color( $input['primary'] ) ?: '#2271b1';
		}
		if ( isset( $input['sidebar_bg'] ) ) {
			$out['sidebar_bg'] = sanitize_hex_color( $input['sidebar_bg'] ) ?: '#1d2327';
		}
		return $out;
	}

	/**
	 * Sanitize dark mode global setting.
	 *
	 * @param  mixed $value Raw value.
	 * @return string       'off', 'force_on', or 'force_off'.
	 */
	public function sanitize_dark_mode_global( $value ) {
		$allowed = array( 'off', 'force_on', 'force_off' );
		return in_array( $value, $allowed, true ) ? $value : 'off';
	}


	/**
	 * Sanitize a checkbox (returns 1 or 0).
	 *
	 * @param  mixed $value Raw value.
	 * @return int          1 or 0.
	 */
	public function sanitize_checkbox( $value ) {
		return $value ? 1 : 0;
	}

	/**
	 * Sanitize a flat array of strings.
	 *
	 * @param  mixed $input Raw input.
	 * @return array        Indexed array of sanitized strings.
	 */
	public function sanitize_str_array( $input ) {
		if ( ! is_array( $input ) ) {
			return array();
		}
		return array_values( array_filter( array_map( 'sanitize_text_field', $input ) ) );
	}

		/**
	 * Sanitize a key-value array (menu_slug => custom label).
	 *
	 * Important: menu slugs may contain characters such as `.php`, `?`, `=`, `&`.
	 * Do not use sanitize_key() here or the original slug will be altered and
	 * the rename lookup will stop matching the real WordPress menu slug.
	 *
	 * @param  mixed $input Raw input.
	 * @return array        Associative array: sanitized_menu_slug => sanitized_label.
	 */
	public function sanitize_kv_array( $input ) {
		if ( ! is_array( $input ) ) {
			return array();
		}

		$pnlrdb_out = array();

		foreach ( $input as $pnlrdb_menu_slug => $pnlrdb_label ) {
			$pnlrdb_clean_slug  = sanitize_text_field( wp_unslash( (string) $pnlrdb_menu_slug ) );
			$pnlrdb_clean_label = sanitize_text_field( wp_unslash( (string) $pnlrdb_label ) );

			if ( '' !== $pnlrdb_clean_slug && '' !== $pnlrdb_clean_label ) {
				$pnlrdb_out[ $pnlrdb_clean_slug ] = $pnlrdb_clean_label;
			}
		}

		return $pnlrdb_out;
	}

	/**
	 * Sanitize role menu visibility array.
	 * Structure: array( role_slug => array( menu_slug, ... ) )
	 *
	 * @param  mixed $input Raw input.
	 * @return array        Sanitized array.
	 */
	public function sanitize_role_vis( $input ) {
		if ( ! is_array( $input ) ) {
			return array();
		}
		$out = array();
		foreach ( $input as $role => $slugs ) {
			$role = sanitize_key( $role );
			if ( $role && is_array( $slugs ) ) {
				$out[ $role ] = array_values( array_filter( array_map( 'sanitize_text_field', $slugs ) ) );
			}
		}
		return $out;
	}
}
