<?php
/**
 * Role Manager
 *
 * Handles the Role Manager tab on the Menu Builder page: role cloning,
 * capability editing, and plugin access grants. AJAX handlers are added
 * in later phases. This phase registers the class in the boot sequence only.
 *
 * @package Agency_Pulse_Admin_Menu
 * @since   1.0.0
 */

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

class Agency_Pulse_Admin_Menu_Role_Manager {

	/**
	 * Singleton instance.
	 *
	 * @var Agency_Pulse_Admin_Menu_Role_Manager|null
	 */
	private static $instance = null;

	/**
	 * @return Agency_Pulse_Admin_Menu_Role_Manager
	 */
	public static function get_instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor.
	 */
	private function __construct() {
		$this->setup_hooks();
	}

	/** @ignore */
	private function __clone() {}

	/** @ignore */
	public function __wakeup() {}

	/**
	 * Register WordPress hooks.
	 * AJAX actions are wired here in later phases.
	 *
	 * @since 1.0.0
	 */
	private function setup_hooks(): void {
		// Plugin Access AJAX endpoints (registered in class-plugin.php; handlers below).
	}

	// -------------------------------------------------------------------------
	// AJAX handlers — Plugin Access
	// -------------------------------------------------------------------------

	/**
	 * Grant ap_menu_manager to a WordPress role.
	 *
	 * @since 1.0.0
	 */
	public function ajax_grant_role(): void {
		check_ajax_referer( Agency_Pulse_Admin_Menu_Constants::AJAX_RM_NONCE, 'nonce' );
		if ( ! current_user_can( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'agency-pulse-admin-menu' ) ) );
		}
		$slug = isset( $_POST['role'] ) ? sanitize_key( wp_unslash( $_POST['role'] ) ) : '';
		$role = get_role( $slug );
		if ( ! $role ) {
			wp_send_json_error( array( 'message' => __( 'Role not found.', 'agency-pulse-admin-menu' ) ) );
		}
		$role->add_cap( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS, true );
		wp_send_json_success( $this->get_plugin_access_list() );
	}

	/**
	 * Revoke ap_menu_manager from a WordPress role.
	 *
	 * @since 1.0.0
	 */
	public function ajax_revoke_role(): void {
		check_ajax_referer( Agency_Pulse_Admin_Menu_Constants::AJAX_RM_NONCE, 'nonce' );
		if ( ! current_user_can( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'agency-pulse-admin-menu' ) ) );
		}
		$slug = isset( $_POST['role'] ) ? sanitize_key( wp_unslash( $_POST['role'] ) ) : '';
		$role = get_role( $slug );
		if ( ! $role ) {
			wp_send_json_error( array( 'message' => __( 'Role not found.', 'agency-pulse-admin-menu' ) ) );
		}
		$role->remove_cap( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS );
		wp_send_json_success( $this->get_plugin_access_list() );
	}

	/**
	 * Grant ap_menu_manager to a specific user (user-level override).
	 *
	 * @since 1.0.0
	 */
	public function ajax_grant_user(): void {
		check_ajax_referer( Agency_Pulse_Admin_Menu_Constants::AJAX_RM_NONCE, 'nonce' );
		if ( ! current_user_can( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'agency-pulse-admin-menu' ) ) );
		}
		$user_id = isset( $_POST['user_id'] ) ? absint( wp_unslash( $_POST['user_id'] ) ) : 0;
		$user    = get_userdata( $user_id );
		if ( ! $user ) {
			wp_send_json_error( array( 'message' => __( 'User not found.', 'agency-pulse-admin-menu' ) ) );
		}
		$user->add_cap( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS, true );
		wp_send_json_success( $this->get_plugin_access_list() );
	}

	/**
	 * Search users by login, display name, or email for the autocomplete field.
	 *
	 * @since 1.0.0
	 */
	public function ajax_search_users(): void {
		check_ajax_referer( Agency_Pulse_Admin_Menu_Constants::AJAX_RM_NONCE, 'nonce' );
		if ( ! current_user_can( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS ) ) {
			wp_send_json_error();
		}
		$term = isset( $_POST['term'] ) ? sanitize_text_field( wp_unslash( $_POST['term'] ) ) : '';
		if ( strlen( $term ) < 2 ) {
			wp_send_json_success( array() );
		}
		$query = new WP_User_Query( array(
			'search'         => '*' . $term . '*',
			'search_columns' => array( 'user_login', 'user_email', 'display_name' ),
			'number'         => 8,
		) );
		$results = array();
		foreach ( $query->get_results() as $user ) {
			$results[] = array(
				'id'           => (int) $user->ID,
				'display_name' => $user->display_name,
				'email'        => $user->user_email,
			);
		}
		wp_send_json_success( $results );
	}

	/**
	 * Revoke ap_menu_manager from a specific user.
	 *
	 * @since 1.0.0
	 */
	public function ajax_revoke_user(): void {
		check_ajax_referer( Agency_Pulse_Admin_Menu_Constants::AJAX_RM_NONCE, 'nonce' );
		if ( ! current_user_can( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'agency-pulse-admin-menu' ) ) );
		}
		$user_id = isset( $_POST['user_id'] ) ? absint( wp_unslash( $_POST['user_id'] ) ) : 0;
		$user    = get_userdata( $user_id );
		if ( ! $user ) {
			wp_send_json_error( array( 'message' => __( 'User not found.', 'agency-pulse-admin-menu' ) ) );
		}
		$user->remove_cap( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS );
		wp_send_json_success( $this->get_plugin_access_list() );
	}

	// -------------------------------------------------------------------------
	// Data retrieval
	// -------------------------------------------------------------------------

	/**
	 * WordPress built-in roles. These are protected from deletion.
	 *
	 * @since 1.0.0
	 */
	const BUILTIN_ROLES = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );

	/**
	 * Return all roles and users currently holding the ap_menu_manager capability.
	 *
	 * Roles are detected by checking the role definition directly.
	 * Users are detected by querying wp_usermeta for an explicit user-level grant
	 * (stored in the serialised wp_capabilities meta key). Users who have the cap
	 * only via their role are NOT returned here — they appear in the roles list.
	 *
	 * @since 1.0.0
	 * @return array {
	 *   @type array[] $roles  Each entry: slug, name, is_builtin.
	 *   @type array[] $users  Each entry: id, display_name, email.
	 * }
	 */
	public function get_plugin_access_list(): array {
		global $wpdb;

		$cap = Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS;

		// Roles whose definition includes the cap.
		$roles = array();
		foreach ( get_editable_roles() as $slug => $role_data ) {
			if ( ! empty( $role_data['capabilities'][ $cap ] ) ) {
				$roles[] = array(
					'slug'       => $slug,
					'name'       => translate_user_role( $role_data['name'] ),
					'is_builtin' => in_array( $slug, self::BUILTIN_ROLES, true ),
				);
			}
		}

		// Users with an explicit user-level cap grant (not via role).
		$user_query = new WP_User_Query( array(
			'meta_query' => array(
				array(
					'key'     => $wpdb->prefix . 'capabilities',
					'value'   => '"' . $cap . '"',
					'compare' => 'LIKE',
				),
			),
			'number' => 100,
		) );

		$users = array();
		foreach ( $user_query->get_results() as $user ) {
			$users[] = array(
				'id'           => (int) $user->ID,
				'display_name' => $user->display_name,
				'email'        => $user->user_email,
			);
		}

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

	/**
	 * Return all registered roles formatted for the Role Editor list.
	 *
	 * Built-in roles are listed first, then custom roles alphabetically.
	 *
	 * @since 1.0.0
	 * @return array[] Each entry: slug, name, cap_count, is_builtin.
	 */
	public function get_roles_for_display(): array {
		$user_counts   = count_users();
		$role_user_map = $user_counts['avail_roles'];
		$roles = array();
		foreach ( get_editable_roles() as $slug => $role_data ) {
			$roles[] = array(
				'slug'       => $slug,
				'name'       => translate_user_role( $role_data['name'] ),
				'cap_count'  => count( array_filter( $role_data['capabilities'] ) ),
				'user_count' => isset( $role_user_map[ $slug ] ) ? (int) $role_user_map[ $slug ] : 0,
				'is_builtin' => in_array( $slug, self::BUILTIN_ROLES, true ),
			);
		}
		usort( $roles, static function( $a, $b ) {
			if ( $a['is_builtin'] !== $b['is_builtin'] ) {
				return $a['is_builtin'] ? -1 : 1;
			}
			return strcmp( $a['name'], $b['name'] );
		} );
		return $roles;
	}

	// -------------------------------------------------------------------------
	// AJAX handlers — Capability Editor
	// -------------------------------------------------------------------------

	/**
	 * Return a role's capabilities grouped for the capability editor.
	 *
	 * @since 1.0.0
	 */
	public function ajax_get_role_caps(): void {
		check_ajax_referer( Agency_Pulse_Admin_Menu_Constants::AJAX_RM_NONCE, 'nonce' );
		if ( ! current_user_can( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'agency-pulse-admin-menu' ) ) );
		}
		$slug = isset( $_POST['slug'] ) ? sanitize_key( wp_unslash( $_POST['slug'] ) ) : '';
		$role = get_role( $slug );
		if ( ! $role ) {
			wp_send_json_error( array( 'message' => __( 'Role not found.', 'agency-pulse-admin-menu' ) ) );
		}
		$roles_data = get_editable_roles();
		$role_name  = isset( $roles_data[ $slug ]['name'] )
			? translate_user_role( $roles_data[ $slug ]['name'] )
			: $slug;
		wp_send_json_success( array(
			'slug'       => $slug,
			'role_name'  => $role_name,
			'is_builtin' => in_array( $slug, self::BUILTIN_ROLES, true ),
			'groups'     => $this->group_capabilities( $role->capabilities ),
		) );
	}

	/**
	 * Sort and group a capabilities array into labelled sections for the editor UI.
	 *
	 * @since 1.0.0
	 * @param  array $caps  Role capabilities map: name => bool.
	 * @return array        Ordered groups: [{label, caps:[{name,granted}]}].
	 */
	private function group_capabilities( array $caps ): array {
		$core_list = $this->get_core_wp_caps();
		$core      = array();
		$other     = array();
		foreach ( $caps as $name => $granted ) {
			$entry = array( 'name' => $name, 'granted' => (bool) $granted );
			if ( in_array( $name, $core_list, true ) ) {
				$core[] = $entry;
			} else {
				$other[] = $entry;
			}
		}
		usort( $core,  static function( $a, $b ) { return strcmp( $a['name'], $b['name'] ); } );
		usort( $other, static function( $a, $b ) { return strcmp( $a['name'], $b['name'] ); } );
		$groups = array();
		if ( ! empty( $core ) ) {
			$groups[] = array(
				'label' => __( 'WordPress Core', 'agency-pulse-admin-menu' ),
				'caps'  => $core,
			);
		}
		if ( ! empty( $other ) ) {
			$groups[] = array(
				'label' => __( 'Other Capabilities', 'agency-pulse-admin-menu' ),
				'caps'  => $other,
			);
		}
		return $groups;
	}

	/**
	 * Hardcoded list of standard WordPress core capabilities.
	 *
	 * @since 1.0.0
	 * @return string[]
	 */
	private function get_core_wp_caps(): array {
		return array(
			'activate_plugins', 'create_users', 'customize',
			'delete_others_pages', 'delete_others_posts', 'delete_pages', 'delete_plugins',
			'delete_posts', 'delete_private_pages', 'delete_private_posts',
			'delete_published_pages', 'delete_published_posts', 'delete_themes', 'delete_users',
			'edit_dashboard', 'edit_files', 'edit_others_pages', 'edit_others_posts',
			'edit_pages', 'edit_plugins', 'edit_posts', 'edit_private_pages',
			'edit_private_posts', 'edit_published_pages', 'edit_published_posts',
			'edit_theme_options', 'edit_themes', 'edit_users', 'export', 'import',
			'install_plugins', 'install_themes', 'list_users', 'manage_categories',
			'manage_links', 'manage_options', 'moderate_comments', 'promote_users',
			'publish_pages', 'publish_posts', 'read', 'read_private_pages',
			'read_private_posts', 'remove_users', 'switch_themes', 'unfiltered_html',
			'unfiltered_upload', 'update_core', 'update_plugins', 'update_themes',
			'upload_files',
		);
	}

	/**
	 * Persist capability changes for a role.
	 *
	 * Receives a JSON-encoded map of { cap_name: 1|0 } for all caps shown in the editor.
	 * Calls add_cap(true) for granted caps and remove_cap() for revoked ones.
	 *
	 * @since 1.0.0
	 */
	public function ajax_save_role_caps(): void {
		check_ajax_referer( Agency_Pulse_Admin_Menu_Constants::AJAX_RM_NONCE, 'nonce' );
		if ( ! current_user_can( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'agency-pulse-admin-menu' ) ) );
		}
		$slug = isset( $_POST['slug'] ) ? sanitize_key( wp_unslash( $_POST['slug'] ) ) : '';
		$role = get_role( $slug );
		if ( ! $role ) {
			wp_send_json_error( array( 'message' => __( 'Role not found.', 'agency-pulse-admin-menu' ) ) );
		}
		$caps_raw = isset( $_POST['caps'] ) ? wp_unslash( $_POST['caps'] ) : '{}';
		$caps_map = json_decode( $caps_raw, true );
		if ( ! is_array( $caps_map ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid capability data.', 'agency-pulse-admin-menu' ) ) );
		}
		foreach ( $caps_map as $cap_name => $granted ) {
			$cap_name = sanitize_key( (string) $cap_name );
			if ( ! $cap_name ) {
				continue;
			}
			if ( $granted ) {
				$role->add_cap( $cap_name, true );
			} else {
				$role->remove_cap( $cap_name );
			}
		}
		$cap_count = count( array_filter( get_role( $slug )->capabilities ) );
		wp_send_json_success( array(
			'slug'      => $slug,
			'cap_count' => $cap_count,
		) );
	}
	/**
	 * Clone an existing role into a new custom role with all capabilities copied.
	 *
	 * @since 1.0.0
	 */
	public function ajax_clone_role(): void {
		check_ajax_referer( Agency_Pulse_Admin_Menu_Constants::AJAX_RM_NONCE, 'nonce' );
		if ( ! current_user_can( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'agency-pulse-admin-menu' ) ) );
		}
		$source_slug = isset( $_POST['source_slug'] ) ? sanitize_key( wp_unslash( $_POST['source_slug'] ) ) : '';
		$new_name    = isset( $_POST['new_name'] ) ? sanitize_text_field( wp_unslash( $_POST['new_name'] ) ) : '';
		$new_slug    = isset( $_POST['new_slug'] ) ? sanitize_key( wp_unslash( $_POST['new_slug'] ) ) : '';
		if ( ! $new_name ) {
			wp_send_json_error( array( 'message' => __( 'Role name is required.', 'agency-pulse-admin-menu' ) ) );
		}
		if ( ! $new_slug || ! preg_match( '/^[a-z][a-z0-9_]*$/', $new_slug ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid role slug.', 'agency-pulse-admin-menu' ) ) );
		}
		if ( get_role( $new_slug ) ) {
			wp_send_json_error( array( 'message' => __( 'A role with this slug already exists.', 'agency-pulse-admin-menu' ) ) );
		}
		if ( $source_slug ) {
			$source_role = get_role( $source_slug );
			if ( ! $source_role ) {
				wp_send_json_error( array( 'message' => __( 'Source role not found.', 'agency-pulse-admin-menu' ) ) );
			}
			$caps = $source_role->capabilities;
		} else {
			$caps = array();
		}
		$new_role = add_role( $new_slug, $new_name, $caps );
		if ( ! $new_role ) {
			wp_send_json_error( array( 'message' => __( 'Could not create role. The slug may already be in use.', 'agency-pulse-admin-menu' ) ) );
		}
		wp_send_json_success( array(
			'slug'       => $new_slug,
			'name'       => $new_name,
			'cap_count'  => count( array_filter( $caps ) ),
			'is_builtin' => false,
		) );
	}
	/**
	 * Delete a custom role and clean up its menu assignment entry.
	 *
	 * Built-in roles are blocked server-side regardless of UI state.
	 *
	 * @since 1.0.0
	 */
	public function ajax_delete_role(): void {
		check_ajax_referer( Agency_Pulse_Admin_Menu_Constants::AJAX_RM_NONCE, 'nonce' );
		if ( ! current_user_can( Agency_Pulse_Admin_Menu_Constants::CAP_MANAGE_MENUS ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'agency-pulse-admin-menu' ) ) );
		}
		$slug = isset( $_POST['slug'] ) ? sanitize_key( wp_unslash( $_POST['slug'] ) ) : '';
		if ( ! $slug ) {
			wp_send_json_error( array( 'message' => __( 'Role slug is required.', 'agency-pulse-admin-menu' ) ) );
		}
		if ( in_array( $slug, self::BUILTIN_ROLES, true ) ) {
			wp_send_json_error( array( 'message' => __( 'Built-in roles cannot be deleted.', 'agency-pulse-admin-menu' ) ) );
		}
		if ( ! get_role( $slug ) ) {
			wp_send_json_error( array( 'message' => __( 'Role not found.', 'agency-pulse-admin-menu' ) ) );
		}
		remove_role( $slug );
		$assignments = get_option( Agency_Pulse_Admin_Menu_Constants::OPTION_ROLE_ASSIGNMENTS, array() );
		if ( isset( $assignments[ $slug ] ) ) {
			unset( $assignments[ $slug ] );
			update_option( Agency_Pulse_Admin_Menu_Constants::OPTION_ROLE_ASSIGNMENTS, $assignments );
		}
		wp_send_json_success( array( 'slug' => $slug ) );
	}
}