<?php
/**
 * Role & Menu Manager Module.
 *
 * A safe, decoupled tool for site owners to:
 *   - See every role and its capability count at a glance.
 *   - Hide specific admin-menu items for specific roles (menu tidying), stored
 *     as plugin settings — never by editing core capabilities destructively.
 *
 * SAFETY: this module deliberately does NOT delete roles, strip capabilities
 * from users, or alter role assignments. Menu hiding is purely presentational
 * (it filters the admin menu at display time) and is fully reversible by
 * unchecking — it never touches the database role definitions. This keeps it
 * impossible for routine use of this tool to damage user access.
 *
 * @package Platform\Helm\Modules\Role_Manager
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\Role_Manager;

use Platform\Helm\Contracts\ModuleInterface;

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

final class RoleManagerModule implements ModuleInterface {

	public const OPTION      = 'platform_helm_role_menu_hidden';
	public const PAGE_SLUG   = 'platform-helm-roles';
	public const PARENT_SLUG = 'platform-helm';
	private const CAPABILITY  = 'manage_options';

	public function id(): string {
		return 'role_manager';
	}

	public function name(): string {
		return __( 'Role & Menu Manager', 'platform-helm' );
	}

	public function description(): string {
		return __( 'See every role and its capabilities, and tidy the admin menu per role by hiding items. Presentational and fully reversible — it never deletes roles or strips capabilities. Find it under Helm → Roles & Menus.', 'platform-helm' );
	}

	public function default_enabled(): bool {
		return false;
	}

	public function boot(): void {
		add_action( 'admin_menu', [ $this, 'register_menu' ], 55 );
		add_action( 'admin_init', [ $this, 'maybe_save' ] );
		// Apply menu hiding very late so all items are registered first.
		add_action( 'admin_menu', [ $this, 'apply_menu_hiding' ], 999 );
	}

	public function register_menu(): void {
		if ( ! current_user_can( self::CAPABILITY ) ) {
			return;
		}
		add_submenu_page(
			self::PARENT_SLUG,
			__( 'Roles & Menus', 'platform-helm' ),
			__( 'Roles & Menus', 'platform-helm' ),
			self::CAPABILITY,
			self::PAGE_SLUG,
			[ $this, 'render' ]
		);
	}

	/**
	 * @return array<string,string[]> role => list of hidden top-level menu slugs
	 */
	private function hidden_map(): array {
		$saved = get_option( self::OPTION, [] );
		return is_array( $saved ) ? $saved : [];
	}

	public function maybe_save(): void {
		if ( ! isset( $_POST['platform_helm_roles_save'] ) ) {
			return;
		}
		if ( ! current_user_can( self::CAPABILITY ) ) {
			return;
		}
		check_admin_referer( 'platform_helm_roles', '_roles_nonce' );

		$map = [];
		$submitted = isset( $_POST['hidden'] ) && is_array( $_POST['hidden'] ) ? wp_unslash( $_POST['hidden'] ) : [];
		foreach ( $submitted as $role => $slugs ) {
			$role = sanitize_key( (string) $role );
			if ( '' === $role || ! is_array( $slugs ) ) {
				continue;
			}
			$clean = array_values( array_filter( array_map( 'sanitize_text_field', $slugs ) ) );
			if ( ! empty( $clean ) ) {
				$map[ $role ] = $clean;
			}
		}
		update_option( self::OPTION, $map, false );
		add_settings_error( 'platform_helm_roles', 'saved', __( 'Menu visibility saved.', 'platform-helm' ), 'success' );
	}

	/**
	 * Hide configured top-level menu items for the current user's roles.
	 * Presentational only — does not affect capabilities or direct URL access.
	 */
	public function apply_menu_hiding(): void {
		$user = wp_get_current_user();
		if ( ! $user || ! $user->exists() ) {
			return;
		}
		// Never hide anything from a full admin — avoids locking the owner out.
		if ( user_can( $user, 'manage_options' ) ) {
			return;
		}
		$map   = $this->hidden_map();
		$roles = (array) $user->roles;
		foreach ( $roles as $role ) {
			if ( empty( $map[ $role ] ) || ! is_array( $map[ $role ] ) ) {
				continue;
			}
			foreach ( $map[ $role ] as $slug ) {
				remove_menu_page( (string) $slug );
			}
		}
	}

	public function render(): void {
		if ( ! current_user_can( self::CAPABILITY ) ) {
			wp_die( esc_html__( 'You do not have permission to access this page.', 'platform-helm' ) );
		}
		$roles    = function_exists( 'wp_roles' ) ? wp_roles() : new \WP_Roles();
		$map      = $this->hidden_map();
		$top_menu = $this->top_level_menu_slugs();
		$counts   = count_users();
		$total_menu = count( $top_menu );

		// Highest cap count, to scale the capability-level indicator bars.
		$max_caps = 1;
		foreach ( $roles->roles as $role ) {
			$c = is_array( $role['capabilities'] ?? null ) ? count( array_filter( $role['capabilities'] ) ) : 0;
			$max_caps = max( $max_caps, $c );
		}

		$this->print_styles();
		?>
		<div class="wrap ph-wrap phelm-rm">
			<div class="phelm-rm-head">
				<div>
					<h1 class="phelm-rm-title"><?php esc_html_e( 'Roles & Menus', 'platform-helm' ); ?></h1>
					<p class="phelm-rm-sub"><?php esc_html_e( 'Review every role at a glance, and tidy the admin menu per role. Presentational and fully reversible — capabilities are never changed, and administrators are never affected.', 'platform-helm' ); ?></p>
				</div>
			</div>
			<?php settings_errors( 'platform_helm_roles' ); ?>

			<!-- Roles overview as cards -->
			<div class="phelm-rm-section-label"><?php esc_html_e( 'Roles overview', 'platform-helm' ); ?></div>
			<div class="phelm-rm-rolecards">
				<?php
				foreach ( $roles->roles as $slug => $role ) :
					$cap_count = is_array( $role['capabilities'] ?? null ) ? count( array_filter( $role['capabilities'] ) ) : 0;
					$user_n    = (int) ( $counts['avail_roles'][ $slug ] ?? 0 );
					$pct       = (int) round( ( $cap_count / $max_caps ) * 100 );
					$is_admin  = ( 'administrator' === $slug );
					?>
					<div class="phelm-rm-rolecard">
						<div class="phelm-rm-rolecard-top">
							<span class="phelm-rm-rolename"><?php echo esc_html( (string) $role['name'] ); ?></span>
							<?php if ( $is_admin ) : ?>
								<span class="phelm-rm-chip phelm-rm-chip--locked"><?php esc_html_e( 'Protected', 'platform-helm' ); ?></span>
							<?php endif; ?>
						</div>
						<code class="phelm-rm-roleslug"><?php echo esc_html( $slug ); ?></code>
						<div class="phelm-rm-rolemeta">
							<span><strong><?php echo esc_html( number_format_i18n( $user_n ) ); ?></strong> <?php echo esc_html( _n( 'user', 'users', $user_n, 'platform-helm' ) ); ?></span>
							<span><strong><?php echo esc_html( number_format_i18n( $cap_count ) ); ?></strong> <?php esc_html_e( 'caps', 'platform-helm' ); ?></span>
						</div>
						<div class="phelm-rm-capbar"><span style="width:<?php echo esc_attr( (string) max( 4, $pct ) ); ?>%;"></span></div>
					</div>
				<?php endforeach; ?>
			</div>

			<!-- Menu hiding accordion -->
			<div class="phelm-rm-section-label" style="margin-top:30px;"><?php esc_html_e( 'Hide admin menu items per role', 'platform-helm' ); ?></div>
			<p class="phelm-rm-hint"><?php esc_html_e( 'Expand a role and toggle the menu items you want hidden for it. Administrators are intentionally excluded so you can never lock yourself out.', 'platform-helm' ); ?></p>

			<form method="post" class="phelm-rm-form">
				<?php wp_nonce_field( 'platform_helm_roles', '_roles_nonce' ); ?>

				<div class="phelm-rm-accordion">
				<?php foreach ( $roles->roles as $slug => $role ) :
					if ( 'administrator' === $slug ) { continue; }
					$hidden_here = isset( $map[ $slug ] ) ? (array) $map[ $slug ] : [];
					$hidden_n    = count( array_intersect( $hidden_here, array_keys( $top_menu ) ) );
					$user_n      = (int) ( $counts['avail_roles'][ $slug ] ?? 0 );
					?>
					<div class="phelm-rm-acc-item" data-role="<?php echo esc_attr( $slug ); ?>">
						<button type="button" class="phelm-rm-acc-header" aria-expanded="false">
							<span class="phelm-rm-acc-caret" aria-hidden="true">›</span>
							<span class="phelm-rm-acc-name"><?php echo esc_html( (string) $role['name'] ); ?></span>
							<span class="phelm-rm-acc-usern"><?php echo esc_html( number_format_i18n( $user_n ) ); ?> <?php echo esc_html( _n( 'user', 'users', $user_n, 'platform-helm' ) ); ?></span>
							<span class="phelm-rm-acc-badge <?php echo $hidden_n > 0 ? 'is-active' : ''; ?>">
								<span class="phelm-rm-acc-badge-n"><?php echo esc_html( (string) $hidden_n ); ?></span> / <?php echo esc_html( (string) $total_menu ); ?> <?php esc_html_e( 'hidden', 'platform-helm' ); ?>
							</span>
						</button>
						<div class="phelm-rm-acc-body" hidden>
							<div class="phelm-rm-acc-toolbar">
								<input type="search" class="phelm-rm-search" placeholder="<?php esc_attr_e( 'Filter menu items…', 'platform-helm' ); ?>">
								<div class="phelm-rm-bulk">
									<button type="button" class="button-link phelm-rm-all"><?php esc_html_e( 'Hide all', 'platform-helm' ); ?></button>
									<span class="phelm-rm-sep">·</span>
									<button type="button" class="button-link phelm-rm-none"><?php esc_html_e( 'Show all', 'platform-helm' ); ?></button>
								</div>
							</div>
							<div class="phelm-rm-pillgrid">
								<?php foreach ( $top_menu as $menu_slug => $menu_label ) :
									$checked = in_array( $menu_slug, $hidden_here, true );
									?>
									<label class="phelm-rm-pill <?php echo $checked ? 'is-hidden' : ''; ?>" data-label="<?php echo esc_attr( strtolower( $menu_label ) ); ?>">
										<input type="checkbox" name="hidden[<?php echo esc_attr( $slug ); ?>][]" value="<?php echo esc_attr( $menu_slug ); ?>" <?php checked( $checked ); ?>>
										<span class="phelm-rm-pill-dot" aria-hidden="true"></span>
										<span class="phelm-rm-pill-label"><?php echo esc_html( $menu_label ); ?></span>
										<span class="phelm-rm-pill-state"><?php esc_html_e( 'hidden', 'platform-helm' ); ?></span>
									</label>
								<?php endforeach; ?>
							</div>
						</div>
					</div>
				<?php endforeach; ?>
				</div>

				<div class="phelm-rm-savebar">
					<button type="submit" name="platform_helm_roles_save" class="button button-primary button-hero"><?php esc_html_e( 'Save menu visibility', 'platform-helm' ); ?></button>
					<span class="phelm-rm-savehint"><?php esc_html_e( 'Changes apply the next time each user loads the admin.', 'platform-helm' ); ?></span>
				</div>
			</form>
		</div>
		<?php
		$this->print_script();
	}

	private function print_styles(): void {
		?>
		<style id="platform-helm-rm-styles">
		.phelm-rm{--b:#0047AB;--b2:#2C6BD9;--bd:#003680;--tint:#EAF1FB;--ink:#0F1A33;--ink2:#5C6B82;--muted:#8A95A8;--bg:#fff;--soft:#F7F9FC;--line:#E5EAF2;--ok:#1F8A5A;--okt:#E4F7EE;max-width:1180px;}
		.phelm-rm *{box-sizing:border-box;}
		.phelm-rm-head{margin:10px 0 18px;}
		.phelm-rm-title{font-size:23px;font-weight:700;letter-spacing:-.4px;margin:0;color:var(--ink);}
		.phelm-rm-sub{margin:6px 0 0;font-size:13px;color:var(--ink2);max-width:760px;}
		.phelm-rm-section-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--ink2);margin:6px 0 12px;}
		.phelm-rm-hint{font-size:12.5px;color:var(--ink2);margin:0 0 14px;max-width:760px;}

		/* Role overview cards */
		.phelm-rm-rolecards{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:12px;}
		.phelm-rm-rolecard{background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:14px 16px;box-shadow:0 1px 2px rgba(15,26,51,.04);transition:transform .15s,box-shadow .15s,border-color .15s;}
		.phelm-rm-rolecard:hover{transform:translateY(-2px);box-shadow:0 6px 16px rgba(0,71,171,.08);border-color:#C9D8EF;}
		.phelm-rm-rolecard-top{display:flex;align-items:center;justify-content:space-between;gap:8px;}
		.phelm-rm-rolename{font-size:14px;font-weight:700;color:var(--ink);}
		.phelm-rm-roleslug{display:inline-block;font-size:10.5px;color:var(--muted);background:var(--soft);border:1px solid var(--line);border-radius:4px;padding:1px 6px;margin-top:6px;}
		.phelm-rm-rolemeta{display:flex;gap:14px;margin:12px 0 8px;font-size:12px;color:var(--ink2);}
		.phelm-rm-rolemeta strong{color:var(--ink);font-variant-numeric:tabular-nums;}
		.phelm-rm-capbar{height:5px;border-radius:99px;background:var(--soft);overflow:hidden;}
		.phelm-rm-capbar span{display:block;height:100%;background:linear-gradient(90deg,var(--b),var(--b2));border-radius:99px;}
		.phelm-rm-chip{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;padding:2px 7px;border-radius:99px;}
		.phelm-rm-chip--locked{background:var(--tint);color:var(--b);border:1px solid #C9D8EF;}

		/* Accordion */
		.phelm-rm-accordion{display:flex;flex-direction:column;gap:10px;}
		.phelm-rm-acc-item{background:var(--bg);border:1px solid var(--line);border-radius:10px;overflow:hidden;box-shadow:0 1px 2px rgba(15,26,51,.04);}
		.phelm-rm-acc-item.is-open{border-color:#C9D8EF;box-shadow:0 4px 14px rgba(0,71,171,.07);}
		.phelm-rm-acc-header{display:flex;align-items:center;gap:12px;width:100%;background:transparent;border:0;padding:14px 16px;cursor:pointer;text-align:left;font:inherit;}
		.phelm-rm-acc-header:hover{background:var(--soft);}
		.phelm-rm-acc-caret{font-size:18px;color:var(--muted);transition:transform .18s;line-height:1;}
		.phelm-rm-acc-item.is-open .phelm-rm-acc-caret{transform:rotate(90deg);color:var(--b);}
		.phelm-rm-acc-name{font-size:14px;font-weight:700;color:var(--ink);flex:1;}
		.phelm-rm-acc-usern{font-size:11.5px;color:var(--muted);}
		.phelm-rm-acc-badge{font-size:11px;color:var(--ink2);background:var(--soft);border:1px solid var(--line);border-radius:99px;padding:3px 10px;font-variant-numeric:tabular-nums;}
		.phelm-rm-acc-badge.is-active{background:var(--tint);border-color:#C9D8EF;color:var(--b);font-weight:600;}
		.phelm-rm-acc-badge-n{font-weight:700;}
		.phelm-rm-acc-body{padding:4px 16px 18px;border-top:1px solid var(--line);}
		.phelm-rm-acc-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:14px 0;flex-wrap:wrap;}
		.phelm-rm-search{min-width:220px;flex:1;max-width:320px;padding:6px 10px;border:1px solid var(--line);border-radius:7px;font-size:13px;}
		.phelm-rm-search:focus{outline:none;border-color:var(--b2);box-shadow:0 0 0 3px var(--tint);}
		.phelm-rm-bulk{font-size:12px;color:var(--muted);}
		.phelm-rm-bulk .button-link{font-size:12px;color:var(--b);text-decoration:none;font-weight:600;}
		.phelm-rm-bulk .button-link:hover{color:var(--bd);text-decoration:underline;}
		.phelm-rm-sep{margin:0 6px;color:var(--line);}

		/* Toggle pills */
		.phelm-rm-pillgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:8px;}
		.phelm-rm-pill{display:flex;align-items:center;gap:9px;padding:9px 12px;border:1px solid var(--line);border-radius:8px;cursor:pointer;background:var(--bg);transition:all .14s;user-select:none;}
		.phelm-rm-pill:hover{border-color:#C9D8EF;background:var(--soft);}
		.phelm-rm-pill input{position:absolute;opacity:0;pointer-events:none;}
		.phelm-rm-pill-dot{width:16px;height:16px;border-radius:5px;border:2px solid #C7CFDD;flex-shrink:0;position:relative;transition:all .14s;}
		.phelm-rm-pill-label{font-size:13px;color:var(--ink);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
		.phelm-rm-pill-state{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:var(--b);opacity:0;transition:opacity .14s;}
		.phelm-rm-pill.is-hidden{border-color:var(--b);background:var(--tint);}
		.phelm-rm-pill.is-hidden .phelm-rm-pill-dot{background:var(--b);border-color:var(--b);}
		.phelm-rm-pill.is-hidden .phelm-rm-pill-dot::after{content:"";position:absolute;left:4px;top:1px;width:4px;height:8px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);}
		.phelm-rm-pill.is-hidden .phelm-rm-pill-state{opacity:1;}
		.phelm-rm-pill.is-filtered{display:none;}

		/* Save bar */
		.phelm-rm-savebar{position:sticky;bottom:0;display:flex;align-items:center;gap:14px;margin-top:18px;padding:14px 16px;background:rgba(255,255,255,.92);backdrop-filter:blur(6px);border:1px solid var(--line);border-radius:10px;box-shadow:0 -2px 10px rgba(15,26,51,.05);}
		.phelm-rm-savehint{font-size:12px;color:var(--muted);}
		@media (max-width:782px){.phelm-rm-savebar{position:static;}}
		</style>
		<?php
	}

	private function print_script(): void {
		?>
		<script>
		(function(){
			var root = document.querySelector('.phelm-rm');
			if(!root) return;

			// Accordion expand/collapse
			root.querySelectorAll('.phelm-rm-acc-header').forEach(function(btn){
				btn.addEventListener('click', function(){
					var item = btn.closest('.phelm-rm-acc-item');
					var body = item.querySelector('.phelm-rm-acc-body');
					var open = item.classList.toggle('is-open');
					btn.setAttribute('aria-expanded', open ? 'true' : 'false');
					if(open){ body.hidden = false; } else { body.hidden = true; }
				});
			});

			function refreshBadge(item){
				var pills = item.querySelectorAll('.phelm-rm-pill input');
				var hidden = 0;
				pills.forEach(function(i){ if(i.checked) hidden++; });
				var badge = item.querySelector('.phelm-rm-acc-badge');
				var nEl = item.querySelector('.phelm-rm-acc-badge-n');
				if(nEl) nEl.textContent = hidden;
				if(badge){ badge.classList.toggle('is-active', hidden > 0); }
			}

			// Pill toggle visual state
			root.querySelectorAll('.phelm-rm-pill').forEach(function(pill){
				var input = pill.querySelector('input');
				input.addEventListener('change', function(){
					pill.classList.toggle('is-hidden', input.checked);
					refreshBadge(pill.closest('.phelm-rm-acc-item'));
				});
			});

			// Per-role search filter
			root.querySelectorAll('.phelm-rm-search').forEach(function(box){
				box.addEventListener('input', function(){
					var q = box.value.toLowerCase().trim();
					var item = box.closest('.phelm-rm-acc-item');
					item.querySelectorAll('.phelm-rm-pill').forEach(function(pill){
						var label = pill.getAttribute('data-label') || '';
						pill.classList.toggle('is-filtered', q !== '' && label.indexOf(q) === -1);
					});
				});
			});

			// Bulk hide all / show all (respects current filter)
			function setAll(item, val){
				item.querySelectorAll('.phelm-rm-pill').forEach(function(pill){
					if(pill.classList.contains('is-filtered')) return;
					var input = pill.querySelector('input');
					input.checked = val;
					pill.classList.toggle('is-hidden', val);
				});
				refreshBadge(item);
			}
			root.querySelectorAll('.phelm-rm-all').forEach(function(b){
				b.addEventListener('click', function(){ setAll(b.closest('.phelm-rm-acc-item'), true); });
			});
			root.querySelectorAll('.phelm-rm-none').forEach(function(b){
				b.addEventListener('click', function(){ setAll(b.closest('.phelm-rm-acc-item'), false); });
			});
		})();
		</script>
		<?php
	}

	/**
	 * Capture the current top-level admin menu as slug => label.
	 *
	 * @return array<string,string>
	 */
	private function top_level_menu_slugs(): array {
		global $menu;
		$out = [];
		if ( is_array( $menu ) ) {
			foreach ( $menu as $item ) {
				if ( empty( $item[0] ) || empty( $item[2] ) ) {
					continue;
				}
				$label = trim( (string) preg_replace( '/<[^>]*>.*$/', '', (string) $item[0] ) );
				if ( '' === $label ) {
					continue;
				}
				$out[ (string) $item[2] ] = $label;
			}
		}
		return $out;
	}
}
