<?php

defined( 'ABSPATH' ) || exit;

/**
 * Admin Menu by Role module.
 *
 * Declutter the wp-admin sidebar for client editors. Per role, choose which
 * top-level admin menu items should be hidden. A client logging in as Editor
 * might see Posts / Pages / Media only; an admin sees everything.
 *
 * NOTE: this is distinct from the frontend `menu_visibility` module — that
 * one hides navigation menu items on the front end. This one hides the
 * wp-admin sidebar.
 *
 * Mechanics:
 *  - On `admin_menu` priority 999 (after every plugin has registered its
 *    menus), iterate the current user's roles, union the per-role hide
 *    lists into a single set, and call `remove_menu_page()` for each slug.
 *  - Submenu hiding is intentionally out of scope for v1 — top-level only.
 *  - Administrators are subject to the same rules as any other role; if
 *    rian wants to declutter his own view he can configure it. Recovery hatch
 *    via `BW_DEV_ADMIN_MENU_ROLE_DISABLE` constant in `wp-config.php` bypasses
 *    the module entirely.
 *
 * The settings tab enumerates WordPress's global admin-menu array (top-level
 * items registered for the user viewing the settings page — that user is an
 * admin and sees everything, so we can use it as the canonical list). One
 * fieldset per registered role; checkbox per menu item.
 *
 * @package BW_Dev
 */

class BW_Dev_Module_Admin_Menu_Role implements BW_Dev_Module_Interface {

	const RECOVERY_CONSTANT = 'BW_DEV_ADMIN_MENU_ROLE_DISABLE';

	public function slug(): string {
		return 'admin_menu_role';
	}

	public function label(): string {
		return __( 'Admin Menu by Role', 'bw-dev' );
	}

	public function group(): string {
		return 'editor_admin';
	}

	public function default_settings(): array {
		return array(
			'roles' => array(),
		);
	}

	public function sanitize( array $data ): array {
		$roles_raw = isset( $data['roles'] ) && is_array( $data['roles'] ) ? $data['roles'] : array();
		$roles     = array();

		foreach ( $roles_raw as $role_slug => $role_payload ) {
			$role_slug = sanitize_key( (string) $role_slug );
			if ( '' === $role_slug ) {
				continue;
			}
			$hide_raw = array();
			if ( is_array( $role_payload ) && isset( $role_payload['hide'] ) && is_array( $role_payload['hide'] ) ) {
				$hide_raw = $role_payload['hide'];
			}
			$hide = array();
			foreach ( $hide_raw as $menu_slug ) {
				$menu_slug = trim( (string) $menu_slug );
				if ( '' === $menu_slug ) {
					continue;
				}
				// Cap length and strip control chars; slugs can contain ?, =, & for query-style submenus.
				$menu_slug = preg_replace( '/[\x00-\x1f\x7f]/', '', $menu_slug );
				$menu_slug = substr( $menu_slug, 0, 200 );
				$hide[]    = $menu_slug;
			}
			$roles[ $role_slug ] = array( 'hide' => array_values( array_unique( $hide ) ) );
		}

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

	public function register(): void {
		add_action( 'admin_menu', array( $this, 'apply_hides' ), 999 );
	}

	public function apply_hides(): void {
		if ( defined( self::RECOVERY_CONSTANT ) && constant( self::RECOVERY_CONSTANT ) ) {
			return;
		}

		$user = wp_get_current_user();
		if ( ! ( $user instanceof WP_User ) || 0 === (int) $user->ID ) {
			return;
		}

		$roles_settings = bw_dev()->settings()->get( $this->slug(), 'roles', array() );
		if ( ! is_array( $roles_settings ) || empty( $roles_settings ) ) {
			return;
		}

		$to_hide = array();
		foreach ( (array) $user->roles as $role ) {
			if ( ! isset( $roles_settings[ $role ] ) ) {
				continue;
			}
			$hide = isset( $roles_settings[ $role ]['hide'] ) && is_array( $roles_settings[ $role ]['hide'] )
				? $roles_settings[ $role ]['hide']
				: array();
			$to_hide = array_merge( $to_hide, $hide );
		}

		foreach ( array_unique( $to_hide ) as $menu_slug ) {
			remove_menu_page( (string) $menu_slug );
		}
	}

	/**
	 * Return the list of top-level menu items currently registered for the
	 * admin viewing the settings page. Each entry is [ slug => label ]. Skips
	 * separators (entries whose slug is empty or whose title is "Separator").
	 */
	private function enumerate_menu_items(): array {
		global $menu;
		$items = array();
		if ( ! is_array( $menu ) ) {
			return $items;
		}
		foreach ( $menu as $item ) {
			if ( ! is_array( $item ) || count( $item ) < 3 ) {
				continue;
			}
			$title = isset( $item[0] ) ? (string) $item[0] : '';
			$slug  = isset( $item[2] ) ? (string) $item[2] : '';
			if ( '' === $slug ) {
				continue;
			}
			if ( 0 === strpos( $slug, 'separator' ) ) {
				continue;
			}
			// Strip update / pending counts that WP appends to admin titles.
			$clean_title = trim( preg_replace( '/<.*$/s', '', wp_strip_all_tags( $title ) ) );
			if ( '' === $clean_title ) {
				$clean_title = $slug;
			}
			$items[ $slug ] = $clean_title;
		}
		return $items;
	}

	public function render_tab(): void {
		$settings    = bw_dev()->settings()->get( $this->slug(), 'roles', array() );
		if ( ! is_array( $settings ) ) {
			$settings = array();
		}
		$menu_items  = $this->enumerate_menu_items();
		$roles       = wp_roles()->roles;
		$is_bypassed = defined( self::RECOVERY_CONSTANT ) && constant( self::RECOVERY_CONSTANT );
		$base        = BW_Dev_Settings::OPTION . '[' . $this->slug() . '][roles]';
		?>
		<p class="description">
			<?php esc_html_e( 'Per role, choose which top-level wp-admin menu items should be hidden. Useful for handing a clean dashboard to clients with the Editor or Author role.', 'bw-dev' ); ?>
		</p>

		<?php if ( $is_bypassed ) : ?>
			<div style="background:#fff;border-left:4px solid #d63638;padding:12px 16px;margin:14px 0;max-width:720px;">
				<strong><?php esc_html_e( 'Recovery mode active', 'bw-dev' ); ?></strong>
				<p style="margin:6px 0 0;">
					<?php
					printf(
						/* translators: %s: PHP constant name */
						esc_html__( 'The %s constant is defined in wp-config.php, so this module is currently bypassed. Remove the constant to re-enable per-role menu hiding.', 'bw-dev' ),
						'<code>' . esc_html( self::RECOVERY_CONSTANT ) . '</code>'
					);
					?>
				</p>
			</div>
		<?php endif; ?>

		<?php if ( empty( $menu_items ) ) : ?>
			<div style="background:#fff;border-left:4px solid #dba617;padding:12px 16px;margin:14px 0;max-width:720px;">
				<strong><?php esc_html_e( 'No menu items detected', 'bw-dev' ); ?></strong>
				<p style="margin:6px 0 0;">
					<?php esc_html_e( 'The global admin menu was empty when this tab rendered — that\'s unusual. Reload the page and try again.', 'bw-dev' ); ?>
				</p>
			</div>
			<?php return; ?>
		<?php endif; ?>

		<?php foreach ( $roles as $role_slug => $role_data ) : ?>
			<?php
			$role_label  = isset( $role_data['name'] ) ? translate_user_role( (string) $role_data['name'] ) : (string) $role_slug;
			$hidden_list = isset( $settings[ $role_slug ]['hide'] ) && is_array( $settings[ $role_slug ]['hide'] )
				? array_flip( $settings[ $role_slug ]['hide'] )
				: array();
			?>
			<fieldset style="margin:18px 0;padding:14px 18px;border:1px solid #c3c4c7;background:#fff;max-width:840px;">
				<legend style="font-weight:600;padding:0 6px;">
					<?php echo esc_html( $role_label ); ?>
					<code style="font-weight:normal;color:#646970;font-size:12px;"><?php echo esc_html( $role_slug ); ?></code>
				</legend>
				<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:6px 18px;">
					<?php foreach ( $menu_items as $menu_slug => $menu_label ) : ?>
						<?php
						$field_name = $base . '[' . $role_slug . '][hide][]';
						$is_hidden  = isset( $hidden_list[ $menu_slug ] );
						?>
						<label style="display:flex;align-items:flex-start;gap:6px;">
							<input type="checkbox" name="<?php echo esc_attr( $field_name ); ?>" value="<?php echo esc_attr( $menu_slug ); ?>" <?php checked( $is_hidden ); ?> />
							<span>
								<?php echo esc_html( $menu_label ); ?>
								<br /><code style="color:#646970;font-size:11px;"><?php echo esc_html( $menu_slug ); ?></code>
							</span>
						</label>
					<?php endforeach; ?>
				</div>
			</fieldset>
		<?php endforeach; ?>

		<h3><?php esc_html_e( 'How multi-role users are handled', 'bw-dev' ); ?></h3>
		<p>
			<?php esc_html_e( 'When a user has more than one role, the module unions the hide lists of all of their roles. If any of their roles hides a menu, the user sees it hidden. To leave administrators untouched, leave the Administrator row blank.', 'bw-dev' ); ?>
		</p>

		<h3><?php esc_html_e( 'If you lock yourself out of Settings', 'bw-dev' ); ?></h3>
		<p>
			<?php
			printf(
				/* translators: %s: PHP constant name */
				esc_html__( 'If you hide %s and can no longer reach Settings → BW Dev, connect via SFTP and add this line to wp-config.php:', 'bw-dev' ),
				'<code>options-general.php</code>'
			);
			?>
		</p>
		<pre style="background:#f6f7f7;padding:10px 14px;max-width:720px;border-left:4px solid #2271b1;"><code>define( '<?php echo esc_html( self::RECOVERY_CONSTANT ); ?>', true );</code></pre>
		<p>
			<?php esc_html_e( 'The module steps aside, every menu reappears. Fix the configuration here, then remove the constant.', 'bw-dev' ); ?>
		</p>
		<?php
	}

	public function uninstall(): void {
		// Settings live under bw_dev_settings — root option drop covers them.
	}
}
