<?php
/**
 * Roles y permisos por pestaña para JB Planeación.
 *
 * @package Logistica
 */

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

/**
 * Gestiona roles operativos, acceso al plugin y pestañas backend/frontend.
 */
final class Logistica_Roles {

	public const CAP_ACCESS        = 'logistica_access';

	public const CAP_MANAGE_CONFIG = 'logistica_manage_config';

	public const OPTION_PERMISSIONS = 'logistica_role_permissions';

	public const OPTION_BACKEND_PAGE = 'logistica_backend_page_id';

	public const USER_META_ROLE = 'logistica_jb_role';

	public const USER_META_TABS = 'logistica_user_tab_overrides';

	/**
	 * @return void
	 */
	public static function init() {
		add_action( 'admin_post_logistica_save_role_permissions', array( __CLASS__, 'handle_admin_save_permissions' ) );
		add_action( 'admin_post_logistica_save_user_jb_profile', array( __CLASS__, 'handle_admin_save_user_jb_profile' ) );
		add_action( 'admin_post_logistica_create_user', array( __CLASS__, 'handle_admin_create_user' ) );
		add_action( 'admin_post_logistica_delete_user_jb', array( __CLASS__, 'handle_admin_delete_user_jb' ) );
		add_action( 'show_user_profile', array( __CLASS__, 'render_user_profile_fields' ) );
		add_action( 'edit_user_profile', array( __CLASS__, 'render_user_profile_fields' ) );
		add_action( 'personal_options_update', array( __CLASS__, 'save_user_profile_fields' ) );
		add_action( 'edit_user_profile_update', array( __CLASS__, 'save_user_profile_fields' ) );
		add_action( 'admin_init', array( __CLASS__, 'maybe_redirect_wp_admin' ) );
		add_filter( 'show_admin_bar', array( __CLASS__, 'filter_admin_bar' ) );
		add_filter( 'login_redirect', array( __CLASS__, 'login_redirect' ), 10, 3 );
		add_action( 'init', array( __CLASS__, 'sync_role_display_names' ), 20 );
		add_action( 'init', array( __CLASS__, 'ensure_backend_page' ), 20 );
	}

	/**
	 * Definición de roles operativos.
	 *
	 * @return array<string, array{label: string, description: string}>
	 */
	public static function get_role_definitions() {
		return array(
			'jb_administrador' => array(
				'label'       => __( 'Administrador JB', 'logistica' ),
				'description' => __( 'Acceso completo a módulos y configuración del plugin.', 'logistica' ),
			),
			'jb_comercial'     => array(
				'label'       => __( 'Comercial JB', 'logistica' ),
				'description' => __( 'Pedidos, cotizaciones y seguimiento comercial.', 'logistica' ),
			),
			'jb_coordinador'   => array(
				'label'       => __( 'Coordinador JB', 'logistica' ),
				'description' => __( 'Planeación de producción, cierres y coordinación general.', 'logistica' ),
			),
			'jb_calidad'       => array(
				'label'       => __( 'Calidad JB', 'logistica' ),
				'description' => __( 'Minutas, características y control de calidad.', 'logistica' ),
			),
			'jb_produccion'    => array(
				'label'       => __( 'Producción JB', 'logistica' ),
				'description' => __( 'Impresión, formado y operación en planta.', 'logistica' ),
			),
			'jb_logistica'     => array(
				'label'       => __( 'Logística / Bodega JB', 'logistica' ),
				'description' => __( 'Inventario, rótulos y movimiento de materiales.', 'logistica' ),
			),
			'jb_colaborador'   => array(
				'label'       => __( 'Colaborador JB', 'logistica' ),
				'description' => __( 'Acceso básico al panel personal y consultas.', 'logistica' ),
			),
		);
	}

	/**
	 * Permisos por defecto al activar el plugin.
	 *
	 * @return array<string, array{backend: list<string>, frontend: list<string>}>
	 */
	public static function default_role_permissions() {
		$all = array_keys( self::tab_choices() );
		$no_config = array_values( array_diff( $all, array( 'logistica-configuracion' ) ) );

		return array(
			'jb_administrador' => array(
				'backend'  => $all,
				'frontend' => $no_config,
			),
			'jb_comercial'     => array(
				'backend'  => array(
					'logistica',
					'logistica-conversiones',
					'logistica-cuadro-pedidos',
					'logistica-cierre-ordenes',
					'logistica-ayuda',
				),
				'frontend' => array(
					'logistica',
					'logistica-cuadro-pedidos',
					'logistica-ayuda',
				),
			),
			'jb_coordinador'   => array(
				'backend'  => array(
					'logistica',
					'logistica-conversiones',
					'logistica-inventario-bodega',
					'logistica-cierre-ordenes',
					'logistica-planeacion-impresion',
					'logistica-planeacion-formado',
					'logistica-tablero-produccion',
					'logistica-cierre-caracteristicas',
					'logistica-cuadro-pedidos',
					'logistica-orden-produccion-vasos',
					'logistica-revision-minuta',
					'logistica-ayuda',
				),
				'frontend' => array(
					'logistica',
					'logistica-inventario-bodega',
					'logistica-cierre-ordenes',
					'logistica-planeacion-impresion',
					'logistica-planeacion-formado',
					'logistica-tablero-produccion',
					'logistica-cuadro-pedidos',
					'logistica-orden-produccion-vasos',
					'logistica-ayuda',
				),
			),
			'jb_calidad'       => array(
				'backend'  => array(
					'logistica',
					'logistica-cierre-ordenes',
					'logistica-cierre-caracteristicas',
					'logistica-revision-minuta',
					'logistica-codigos-etiqueta',
					'logistica-ayuda',
				),
				'frontend' => array(
					'logistica',
					'logistica-cierre-caracteristicas',
					'logistica-revision-minuta',
					'logistica-ayuda',
				),
			),
			'jb_produccion'    => array(
				'backend'  => array(
					'logistica',
					'logistica-planeacion-impresion',
					'logistica-planeacion-formado',
					'logistica-tablero-produccion',
					'logistica-cierre-ordenes',
					'logistica-calculo-tapas',
					'logistica-calculos',
					'logistica-rotulos-rollos',
					'logistica-orden-produccion-vasos',
					'logistica-ayuda',
				),
				'frontend' => array(
					'logistica',
					'logistica-planeacion-impresion',
					'logistica-planeacion-formado',
					'logistica-tablero-produccion',
					'logistica-orden-produccion-vasos',
					'logistica-ayuda',
				),
			),
			'jb_logistica'     => array(
				'backend'  => array(
					'logistica',
					'logistica-inventario-bodega',
					'logistica-rotulos-rollos',
					'logistica-codigos-etiqueta',
					'logistica-cierre-ordenes',
					'logistica-ayuda',
				),
				'frontend' => array(
					'logistica',
					'logistica-inventario-bodega',
					'logistica-rotulos-rollos',
					'logistica-ayuda',
				),
			),
			'jb_colaborador'   => array(
				'backend'  => array(
					'logistica',
					'logistica-ayuda',
				),
				'frontend' => array(
					'logistica',
					'logistica-ayuda',
				),
			),
		);
	}

	/**
	 * @return array<string, string>
	 */
	public static function tab_choices() {
		require_once LOGISTICA_PLUGIN_DIR . 'admin/views/partials.php';

		return logistica_get_nav_tabs();
	}

	/**
	 * @return array<string, array{backend: list<string>, frontend: list<string>}>
	 */
	public static function get_role_permissions() {
		$saved = get_option( self::OPTION_PERMISSIONS, array() );

		if ( ! is_array( $saved ) || empty( $saved ) ) {
			return self::default_role_permissions();
		}

		$defaults = self::default_role_permissions();
		$merged   = array();

		foreach ( self::get_role_definitions() as $role_slug => $role_data ) {
			$merged[ $role_slug ] = array(
				'backend'  => self::sanitize_tab_list( $saved[ $role_slug ]['backend'] ?? ( $defaults[ $role_slug ]['backend'] ?? array() ) ),
				'frontend' => self::sanitize_tab_list( $saved[ $role_slug ]['frontend'] ?? ( $defaults[ $role_slug ]['frontend'] ?? array() ) ),
			);
		}

		return $merged;
	}

	/**
	 * @param array<int, string>|mixed $raw Slugs desde POST.
	 * @return void
	 */
	public static function save_role_permissions( $raw ) {
		if ( ! is_array( $raw ) ) {
			$raw = array();
		}

		$definitions = self::get_role_definitions();
		$saved       = array();

		foreach ( $definitions as $role_slug => $role_data ) {
			$role_raw = isset( $raw[ $role_slug ] ) && is_array( $raw[ $role_slug ] ) ? $raw[ $role_slug ] : array();
			$saved[ $role_slug ] = array(
				'backend'  => self::sanitize_tab_list( $role_raw['backend'] ?? array() ),
				'frontend' => self::sanitize_tab_list( $role_raw['frontend'] ?? array() ),
			);

			if ( empty( $saved[ $role_slug ]['backend'] ) ) {
				$saved[ $role_slug ]['backend'] = array( 'logistica' );
			}
			if ( empty( $saved[ $role_slug ]['frontend'] ) ) {
				$saved[ $role_slug ]['frontend'] = array( 'logistica' );
			}
		}

		update_option( self::OPTION_PERMISSIONS, $saved, false );
		self::sync_role_capabilities();
	}

	/**
	 * Procesa el formulario «Guardar roles y permisos» (admin-post.php).
	 *
	 * @return void
	 */
	public static function handle_admin_save_permissions() {
		if ( ! self::user_can_manage_config() ) {
			wp_die( esc_html__( 'No tienes permisos para guardar roles y permisos.', 'logistica' ) );
		}

		check_admin_referer( 'logistica_save_role_permissions' );

		$raw = isset( $_POST['role_permissions'] ) && is_array( $_POST['role_permissions'] )
			? wp_unslash( $_POST['role_permissions'] )
			: array();

		self::save_role_permissions( $raw );
		self::save_backend_page_id( isset( $_POST['backend_page_id'] ) ? wp_unslash( $_POST['backend_page_id'] ) : 0 );

		require_once LOGISTICA_PLUGIN_DIR . 'admin/views/partials.php';

		$redirect = '';
		if ( ! empty( $_POST['logistica_redirect_to'] ) ) {
			$redirect = esc_url_raw( wp_unslash( (string) $_POST['logistica_redirect_to'] ) );
		}
		if ( '' === $redirect ) {
			$redirect = wp_get_referer();
		}
		if ( ! $redirect ) {
			require_once LOGISTICA_PLUGIN_DIR . 'admin/views/partials.php';
			$redirect = logistica_configuracion_tab_url();
		}

		wp_safe_redirect(
			add_query_arg(
				'logistica_status',
				'roles_saved',
				remove_query_arg( 'logistica_status', $redirect )
			)
		);
		exit;
	}

	/**
	 * Guarda rol JB y pestañas personalizadas (standalone user-edit.php).
	 *
	 * @return void
	 */
	public static function handle_admin_save_user_jb_profile() {
		if ( ! self::user_can_manage_config() ) {
			wp_die( esc_html__( 'No tienes permisos para editar usuarios.', 'logistica' ) );
		}

		check_admin_referer( 'logistica_save_user_jb_profile' );

		$user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
		if ( $user_id <= 0 ) {
			wp_die( esc_html__( 'Usuario no válido.', 'logistica' ) );
		}

		self::save_user_profile_fields( $user_id );

		$redirect = '';
		if ( ! empty( $_POST['logistica_redirect_to'] ) ) {
			$redirect = esc_url_raw( wp_unslash( (string) $_POST['logistica_redirect_to'] ) );
		}
		if ( '' === $redirect ) {
			$redirect = get_edit_user_link( $user_id, logistica_config_return_section() );
		}

		wp_safe_redirect(
			add_query_arg(
				'logistica_status',
				'user_saved',
				remove_query_arg( 'logistica_status', $redirect )
			)
		);
		exit;
	}

	/**
	 * Crea un usuario JB (standalone user-new.php).
	 *
	 * @return void
	 */
	public static function handle_admin_create_user() {
		if ( ! self::user_can_manage_config() ) {
			wp_die( esc_html__( 'No tienes permisos para crear usuarios.', 'logistica' ) );
		}

		check_admin_referer( 'logistica_create_user' );

		require_once LOGISTICA_PLUGIN_DIR . 'admin/views/partials.php';

		$redirect_base = '';
		if ( ! empty( $_POST['logistica_redirect_to'] ) ) {
			$redirect_base = esc_url_raw( wp_unslash( (string) $_POST['logistica_redirect_to'] ) );
		}
		if ( '' === $redirect_base ) {
			$redirect_base = logistica_new_user_url();
		}

		$login        = sanitize_user( wp_unslash( (string) ( $_POST['user_login'] ?? '' ) ), true );
		$email        = sanitize_email( wp_unslash( (string) ( $_POST['user_email'] ?? '' ) ) );
		$display_name = sanitize_text_field( wp_unslash( (string) ( $_POST['display_name'] ?? '' ) ) );
		$password     = (string) wp_unslash( $_POST['user_pass'] ?? '' );
		$password2    = (string) wp_unslash( $_POST['user_pass_confirm'] ?? '' );

		$error = '';
		if ( '' === $login ) {
			$error = 'login_required';
		} elseif ( '' === $email || ! is_email( $email ) ) {
			$error = 'email_invalid';
		} elseif ( strlen( $password ) < 8 ) {
			$error = 'password_short';
		} elseif ( $password !== $password2 ) {
			$error = 'password_mismatch';
		} elseif ( get_user_by( 'login', $login ) ) {
			$error = 'login_exists';
		} elseif ( get_user_by( 'email', $email ) ) {
			$error = 'email_exists';
		}

		if ( '' !== $error ) {
			$clean_redirect = remove_query_arg( 'logistica_error', $redirect_base );
			$clean_redirect = remove_query_arg( 'logistica_status', $clean_redirect );
			wp_safe_redirect( add_query_arg( 'logistica_error', $error, $clean_redirect ) );
			exit;
		}

		if ( '' === $display_name ) {
			$display_name = $login;
		}

		$user_id = JB_Auth::create_user( $login, $password, $email, $display_name, false );
		if ( $user_id <= 0 ) {
			$clean_redirect = remove_query_arg( 'logistica_error', $redirect_base );
			$clean_redirect = remove_query_arg( 'logistica_status', $clean_redirect );
			wp_safe_redirect( add_query_arg( 'logistica_error', 'create_failed', $clean_redirect ) );
			exit;
		}

		self::save_user_profile_fields( $user_id );

		$return_section = logistica_config_return_section();
		$edit_url       = get_edit_user_link( $user_id, $return_section );
		$clean_edit_url = remove_query_arg( 'logistica_error', $edit_url );
		$clean_edit_url = remove_query_arg( 'logistica_status', $clean_edit_url );
		wp_safe_redirect( add_query_arg( 'logistica_status', 'user_created', $clean_edit_url ) );
		exit;
	}

	/**
	 * Envía un usuario JB a la papelera de trazabilidad (standalone).
	 *
	 * @return void
	 */
	public static function handle_admin_delete_user_jb() {
		if ( ! self::user_can_manage_config() ) {
			wp_die( esc_html__( 'No tienes permisos para eliminar usuarios.', 'logistica' ) );
		}

		$user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
		if ( $user_id <= 0 ) {
			wp_die( esc_html__( 'Usuario no válido.', 'logistica' ) );
		}

		check_admin_referer( 'logistica_delete_user_jb_' . $user_id );

		require_once LOGISTICA_PLUGIN_DIR . 'admin/views/partials.php';

		$redirect = '';
		if ( ! empty( $_POST['logistica_redirect_to'] ) ) {
			$redirect = esc_url_raw( wp_unslash( (string) $_POST['logistica_redirect_to'] ) );
		}
		if ( '' === $redirect ) {
			$redirect = logistica_config_section_url( 'logistica-config-usuarios' );
		}

		$clean_redirect = remove_query_arg( array( 'logistica_status', 'logistica_error' ), $redirect );

		if ( ! class_exists( 'Logistica_User_Lifecycle', false ) ) {
			wp_safe_redirect( add_query_arg( 'logistica_error', 'unavailable', $clean_redirect ) );
			exit;
		}

		$result = Logistica_User_Lifecycle::soft_delete( $user_id );
		if ( is_wp_error( $result ) ) {
			wp_safe_redirect(
				add_query_arg(
					'logistica_error',
					sanitize_key( $result->get_error_code() ),
					$clean_redirect
				)
			);
			exit;
		}

		wp_safe_redirect( add_query_arg( 'logistica_status', 'user_deleted', $clean_redirect ) );
		exit;
	}

	/**
	 * @param array<int, string>|mixed $slugs Tab slugs.
	 * @return list<string>
	 */
	private static function sanitize_tab_list( $slugs ) {
		if ( ! is_array( $slugs ) ) {
			return array();
		}

		$choices = self::tab_choices();
		$valid   = array();

		foreach ( $slugs as $slug ) {
			$slug = sanitize_key( (string) $slug );
			if ( '' !== $slug && isset( $choices[ $slug ] ) ) {
				$valid[] = $slug;
			}
		}

		return array_values( array_unique( $valid ) );
	}

	/**
	 * Crea roles y sincroniza capacidades base.
	 *
	 * @return void
	 */
	public static function register_roles() {
		foreach ( self::get_role_definitions() as $role_slug => $role_data ) {
			$existing = get_role( $role_slug );
			$caps     = array(
				'read'                => true,
				self::CAP_ACCESS      => true,
			);

			if ( 'jb_administrador' === $role_slug ) {
				$caps[ self::CAP_MANAGE_CONFIG ] = true;
			}

			if ( $existing ) {
				self::set_role_display_name( $role_slug, $role_data['label'] );
				foreach ( $caps as $cap => $grant ) {
					if ( $grant ) {
						$existing->add_cap( $cap );
					}
				}
			} else {
				add_role( $role_slug, $role_data['label'], $caps );
			}
		}

		$administrator = get_role( 'administrator' );
		if ( $administrator ) {
			$administrator->add_cap( self::CAP_ACCESS );
			$administrator->add_cap( self::CAP_MANAGE_CONFIG );
		}

		self::sync_role_capabilities();
	}

	/**
	 * Sincroniza los nombres visibles de los roles JB en WordPress.
	 *
	 * @return void
	 */
	public static function sync_role_display_names() {
		foreach ( self::get_role_definitions() as $role_slug => $role_data ) {
			if ( get_role( $role_slug ) ) {
				self::set_role_display_name( $role_slug, $role_data['label'] );
			}
		}
	}

	/**
	 * @param string $role_slug Role slug.
	 * @param string $label     Display name.
	 * @return void
	 */
	private static function set_role_display_name( $role_slug, $label ) {
		if ( ! function_exists( 'wp_roles' ) ) {
			return;
		}

		$wp_roles = wp_roles();
		if ( ! isset( $wp_roles->roles[ $role_slug ] ) ) {
			return;
		}

		$current = (string) ( $wp_roles->roles[ $role_slug ]['name'] ?? '' );
		if ( $current === $label ) {
			return;
		}

		$wp_roles->roles[ $role_slug ]['name'] = $label;
		if ( isset( $wp_roles->role_names[ $role_slug ] ) ) {
			$wp_roles->role_names[ $role_slug ] = $label;
		}
		update_option( 'jb_wp_roles', $wp_roles->roles );
	}

	/**
	 * @return void
	 */
	public static function sync_role_capabilities() {
		$permissions = self::get_role_permissions();
		$all_tabs    = array_keys( self::tab_choices() );

		foreach ( self::get_role_definitions() as $role_slug => $role_data ) {
			$role = get_role( $role_slug );
			if ( ! $role ) {
				continue;
			}

			foreach ( $all_tabs as $tab ) {
				$cap = self::tab_capability( $tab );
				$role->remove_cap( $cap );
			}

			$role_tabs = array_unique(
				array_merge(
					$permissions[ $role_slug ]['backend'] ?? array(),
					$permissions[ $role_slug ]['frontend'] ?? array()
				)
			);

			foreach ( $role_tabs as $tab ) {
				$role->add_cap( self::tab_capability( $tab ) );
			}
		}
	}

	/**
	 * @param string $tab Tab slug.
	 * @return string
	 */
	public static function tab_capability( $tab ) {
		return 'logistica_tab_' . str_replace( '-', '_', sanitize_key( $tab ) );
	}

	/**
	 * @param int|null $user_id User ID.
	 * @return bool
	 */
	public static function user_can_access_plugin( $user_id = null ) {
		$user = self::resolve_user( $user_id );
		if ( ! $user ) {
			return false;
		}

		if ( user_can( $user, 'manage_options' ) || user_can( $user, self::CAP_ACCESS ) ) {
			return true;
		}

		return ! empty( self::get_user_jb_roles( $user ) );
	}

	/**
	 * @param int|null $user_id User ID.
	 * @return bool
	 */
	public static function user_can_manage_config( $user_id = null ) {
		$user = self::resolve_user( $user_id );
		if ( ! $user ) {
			return false;
		}

		return user_can( $user, 'manage_options' ) || user_can( $user, self::CAP_MANAGE_CONFIG );
	}

	/**
	 * Roles con permiso de supervisor (eliminación lógica).
	 *
	 * @return list<string>
	 */
	public static function lifecycle_supervisor_roles() {
		return array(
			'jb_administrador',
			'jb_comercial',
			'jb_coordinador',
			'jb_calidad',
			'jb_produccion',
			'jb_logistica',
		);
	}

	/**
	 * @param int|null $user_id User ID.
	 * @return bool
	 */
	public static function user_can_lifecycle_soft_delete( $user_id = null ) {
		if ( self::user_can_lifecycle_purge( $user_id ) ) {
			return true;
		}

		$user = self::resolve_user( $user_id );
		if ( ! $user || ! self::user_can_access_plugin( $user->ID ) ) {
			return false;
		}

		foreach ( self::get_user_jb_roles( $user ) as $role_slug ) {
			if ( 'jb_colaborador' === $role_slug ) {
				return false;
			}
			if ( in_array( $role_slug, self::lifecycle_supervisor_roles(), true ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @param int|null $user_id User ID.
	 * @return bool
	 */
	public static function user_can_lifecycle_restore( $user_id = null ) {
		return self::user_can_lifecycle_purge( $user_id );
	}

	/**
	 * @param int|null $user_id User ID.
	 * @return bool
	 */
	public static function user_can_lifecycle_purge( $user_id = null ) {
		$user = self::resolve_user( $user_id );
		if ( ! $user ) {
			return false;
		}

		if ( user_can( $user, 'manage_options' ) || user_can( $user, self::CAP_MANAGE_CONFIG ) ) {
			return true;
		}

		return in_array( 'jb_administrador', self::get_user_jb_roles( $user ), true );
	}

	/**
	 * @param int|null $user_id User ID.
	 * @return bool
	 */
	public static function user_can_view_lifecycle( $user_id = null ) {
		return self::user_can_manage_config( $user_id );
	}

	/**
	 * @param string   $tab     Tab slug.
	 * @param string   $context backend|frontend.
	 * @param int|null $user_id User ID.
	 * @return bool
	 */
	public static function user_can_tab( $tab, $context = 'backend', $user_id = null ) {
		$tab = sanitize_key( $tab );
		if ( '' === $tab ) {
			return false;
		}

		$user = self::resolve_user( $user_id );
		if ( ! $user ) {
			return false;
		}

		if ( user_can( $user, 'manage_options' ) ) {
			return true;
		}

		if ( 'logistica-configuracion' === $tab ) {
			return self::user_can_manage_config( $user->ID );
		}

		return in_array( $tab, self::get_user_tab_slugs( $context, $user->ID ), true );
	}

	/**
	 * @param string   $context backend|frontend.
	 * @param int|null $user_id User ID.
	 * @return list<string>
	 */
	public static function get_user_tab_slugs( $context = 'backend', $user_id = null ) {
		$context = 'frontend' === $context ? 'frontend' : 'backend';
		$user    = self::resolve_user( $user_id );

		if ( ! $user ) {
			return array();
		}

		if ( user_can( $user, 'manage_options' ) ) {
			$all = array_keys( self::tab_choices() );
			if ( 'frontend' === $context ) {
				return array_values( array_diff( $all, array( 'logistica-configuracion' ) ) );
			}
			return $all;
		}

		$override = get_user_meta( $user->ID, self::USER_META_TABS, true );
		if ( is_array( $override ) && ! empty( $override[ $context ] ) && is_array( $override[ $context ] ) ) {
			$tabs = self::sanitize_tab_list( $override[ $context ] );
			if ( ! empty( $tabs ) && ! in_array( 'logistica-perfil', $tabs, true ) ) {
				$tabs[] = 'logistica-perfil';
			}
			return $tabs;
		}

		$permissions = self::get_role_permissions();
		$tabs        = array();

		foreach ( self::get_user_jb_roles( $user ) as $role_slug ) {
			if ( empty( $permissions[ $role_slug ][ $context ] ) ) {
				continue;
			}
			$tabs = array_merge( $tabs, $permissions[ $role_slug ][ $context ] );
		}

		$tabs = self::sanitize_tab_list( $tabs );

		if ( empty( $tabs ) ) {
			return array();
		}

		$tabs = array_values( array_unique( $tabs ) );
		if ( ! in_array( 'logistica-perfil', $tabs, true ) ) {
			$tabs[] = 'logistica-perfil';
		}

		return $tabs;
	}

	/**
	 * @param WP_User|null $user User object.
	 * @return list<string>
	 */
	public static function get_user_jb_roles( $user = null ) {
		$user = $user instanceof WP_User ? $user : wp_get_current_user();
		if ( ! $user || ! $user->exists() ) {
			return array();
		}

		$assigned = get_user_meta( $user->ID, self::USER_META_ROLE, true );
		if ( is_string( $assigned ) && '' !== $assigned && isset( self::get_role_definitions()[ $assigned ] ) ) {
			return array( $assigned );
		}

		$definitions = array_keys( self::get_role_definitions() );
		$matched     = array();

		foreach ( (array) $user->roles as $role ) {
			if ( in_array( $role, $definitions, true ) ) {
				$matched[] = $role;
			}
		}

		return array_values( array_unique( $matched ) );
	}

	/**
	 * @param int|null $user_id User ID.
	 * @return string
	 */
	public static function get_user_role_label( $user_id = null ) {
		$user = self::resolve_user( $user_id );
		if ( ! $user ) {
			return '';
		}

		$roles = self::get_user_jb_roles( $user );
		if ( ! empty( $roles ) ) {
			$definitions = self::get_role_definitions();
			$labels      = array();

			foreach ( $roles as $role_slug ) {
				if ( isset( $definitions[ $role_slug ] ) ) {
					$labels[] = $definitions[ $role_slug ]['label'];
				}
			}

			if ( ! empty( $labels ) ) {
				return implode( ', ', $labels );
			}
		}

		if ( user_can( $user, 'manage_options' ) ) {
			if ( defined( 'LOGISTICA_STANDALONE' ) && LOGISTICA_STANDALONE ) {
				return __( 'Administrador JB', 'logistica' );
			}

			return __( 'Administrador WordPress', 'logistica' );
		}

		return '';
	}

	/**
	 * @param string $nonce_action Nonce action name.
	 * @return string|null Tab slug or null if only plugin access is required.
	 */
	public static function tab_for_nonce( $nonce_action ) {
		$map = array(
			'logistica_user_panel'                   => 'logistica',
			'logistica_conversion_templates'         => 'logistica-conversiones',
			'logistica_warehouse_inventory'            => 'logistica-inventario-bodega',
			'logistica_cierre_ordenes'                 => 'logistica-cierre-ordenes',
			'logistica_conversion_templates'         => 'logistica-conversiones',
			'logistica_autosave_cierre_ordenes'        => 'logistica-cierre-ordenes',
			'logistica_refresh_cierre_bases'           => 'logistica-cierre-ordenes',
			'logistica_export_cierre_excel'            => 'logistica-cierre-ordenes',
			'logistica_import_cierre_excel'            => 'logistica-cierre-ordenes',
			'logistica_cierre_caracteristicas'         => 'logistica-cierre-caracteristicas',
			'logistica_export_cierre_caracteristicas'  => 'logistica-cierre-caracteristicas',
			'logistica_import_cierre_caracteristicas'  => 'logistica-cierre-caracteristicas',
			'logistica_save_cierre_caracteristica'     => 'logistica-cierre-caracteristicas',
			'logistica_delete_cierre_caracteristica'   => 'logistica-cierre-caracteristicas',
			'logistica_planeacion_impresion'           => 'logistica-planeacion-impresion',
			'logistica_export_planeacion_impresion'    => 'logistica-planeacion-impresion',
			'logistica_planeacion_formado'             => 'logistica-planeacion-formado',
			'logistica_export_planeacion_formado'      => 'logistica-planeacion-formado',
			'logistica_tablero_produccion'             => 'logistica-tablero-produccion',
			'logistica_lifecycle'                      => 'logistica-configuracion',
			'logistica_label_print'                    => 'logistica-codigos-etiqueta',
			'logistica_save_label_print_settings'      => 'logistica-codigos-etiqueta',
			'logistica_save_etiqueta_producto'           => 'logistica-codigos-etiqueta',
			'logistica_delete_etiqueta_producto'       => 'logistica-codigos-etiqueta',
			'logistica_export_etiqueta_productos'      => 'logistica-codigos-etiqueta',
			'logistica_import_etiqueta_productos'      => 'logistica-codigos-etiqueta',
			'logistica_rotulos_rollos'                 => 'logistica-rotulos-rollos',
			'logistica_save_rotulo_rollos'             => 'logistica-rotulos-rollos',
			'logistica_delete_rotulo_rollos'           => 'logistica-rotulos-rollos',
			'logistica_save_rotulo_rollos_print_settings' => 'logistica-rotulos-rollos',
			'logistica_export_rotulo_rollos'           => 'logistica-rotulos-rollos',
			'logistica_import_rotulo_rollos'           => 'logistica-rotulos-rollos',
			'logistica_minutas'                        => 'logistica-revision-minuta',
			'logistica_export_minutas'                 => 'logistica-revision-minuta',
			'logistica_import_minutas'                 => 'logistica-revision-minuta',
			'logistica_pedidos_board'                  => 'logistica-cuadro-pedidos',
			'logistica_export_pedidos_ordenes'         => 'logistica-cuadro-pedidos',
			'logistica_export_pedidos_ordenes_historial' => 'logistica-cuadro-pedidos',
			'logistica_import_pedidos_ordenes'         => 'logistica-cuadro-pedidos',
			'logistica_save_formula_settings'          => 'logistica-configuracion',
			'logistica_save_frontend_menu'             => 'logistica-configuracion',
			'logistica_save_operations_menu_order'     => 'logistica-configuracion',
			'logistica_save_help_settings'             => 'logistica-configuracion',
			'logistica_save_role_permissions'          => 'logistica-configuracion',
			'logistica_save_user_jb_profile'           => 'logistica-configuracion',
			'logistica_create_user'                    => 'logistica-configuracion',
			'logistica_save_panel_settings'            => 'logistica-configuracion',
		);

		return isset( $map[ $nonce_action ] ) ? $map[ $nonce_action ] : null;
	}

	/**
	 * @param string $nonce_action Nonce action.
	 * @return bool
	 */
	public static function current_user_can_action( $nonce_action ) {
		if ( ! self::user_can_access_plugin() ) {
			return false;
		}

		$tab = self::tab_for_nonce( $nonce_action );
		if ( null === $tab ) {
			return true;
		}

		if ( self::user_can_tab( $tab, 'backend' ) ) {
			return true;
		}

		if ( 'logistica-codigos-etiqueta' === $tab && self::user_can_manage_config() ) {
			return true;
		}

		if ( 'logistica-rotulos-rollos' === $tab && self::user_can_manage_config() ) {
			return true;
		}

		return false;
	}

	/**
	 * @return int
	 */
	public static function get_backend_page_id() {
		return max( 0, (int) get_option( self::OPTION_BACKEND_PAGE, 0 ) );
	}

	/**
	 * @param int $page_id Page ID.
	 * @return void
	 */
	public static function save_backend_page_id( $page_id ) {
		$page_id = max( 0, (int) $page_id );
		update_option( self::OPTION_BACKEND_PAGE, $page_id, false );

		if ( $page_id > 0 ) {
			self::assign_backend_page_template( $page_id );
		}
	}

	/**
	 * Asigna la plantilla del plugin a la página de operaciones.
	 *
	 * @param int $page_id Page ID.
	 * @return void
	 */
	public static function assign_backend_page_template( $page_id ) {
		$page_id = max( 0, (int) $page_id );
		if ( $page_id <= 0 ) {
			return;
		}

		$post = get_post( $page_id );
		if ( ! $post instanceof WP_Post || 'page' !== $post->post_type ) {
			return;
		}

		$template = class_exists( 'Logistica_Backend', false )
			? Logistica_Backend::PAGE_TEMPLATE
			: 'jb-planeacion-operaciones.php';

		update_post_meta( $page_id, '_wp_page_template', $template );

		if ( has_shortcode( $post->post_content, 'logistica_backend' ) ) {
			wp_update_post(
				array(
					'ID'           => $page_id,
					'post_content' => '',
				)
			);
		}
	}

	/**
	 * @deprecated 1.5.1 Usar assign_backend_page_template().
	 * @param int $page_id Page ID.
	 * @return void
	 */
	public static function ensure_backend_shortcode_on_page( $page_id ) {
		self::assign_backend_page_template( $page_id );
	}

	/**
	 * Crea o repara la página de operaciones con plantilla del plugin.
	 *
	 * @return int Page ID or 0.
	 */
	public static function ensure_backend_page() {
		global $wp_rewrite;

		if ( ! $wp_rewrite instanceof WP_Rewrite ) {
			return 0;
		}

		$page_id = self::get_backend_page_id();
		if ( $page_id && 'trash' !== get_post_status( $page_id ) ) {
			self::assign_backend_page_template( $page_id );
			return $page_id;
		}

		$slug     = class_exists( 'Logistica_Backend', false ) ? Logistica_Backend::PAGE_SLUG : 'jb-planeacion-backend';
		$existing = get_page_by_path( $slug );
		if ( $existing instanceof WP_Post && 'trash' !== get_post_status( $existing ) ) {
			update_option( self::OPTION_BACKEND_PAGE, (int) $existing->ID );
			self::assign_backend_page_template( (int) $existing->ID );
			return (int) $existing->ID;
		}

		$new_page_id = wp_insert_post(
			array(
				'post_title'   => __( 'JB Planeación — Operaciones', 'logistica' ),
				'post_name'    => $slug,
				'post_content' => '',
				'post_status'  => 'publish',
				'post_type'    => 'page',
			)
		);

		if ( is_wp_error( $new_page_id ) || ! $new_page_id ) {
			return 0;
		}

		update_option( self::OPTION_BACKEND_PAGE, (int) $new_page_id );
		self::assign_backend_page_template( (int) $new_page_id );

		return (int) $new_page_id;
	}

	/**
	 * @return void
	 */
	public static function create_backend_page() {
		self::ensure_backend_page();
	}

	/**
	 * @param string $tab Tab slug.
	 * @return string
	 */
	public static function get_operations_panel_url( $tab = '' ) {
		return self::get_backend_panel_url( $tab );
	}

	/**
	 * @param string $tab Tab slug.
	 * @return string
	 */
	public static function get_backend_panel_url( $tab = '' ) {
		$page_id = self::get_backend_page_id();
		$url     = '';

		if ( $page_id && 'publish' === get_post_status( $page_id ) ) {
			$url = get_permalink( $page_id );
		}

		if ( ! $url ) {
			$page = get_page_by_path( 'jb-planeacion-backend' );
			if ( $page instanceof WP_Post ) {
				$url = get_permalink( $page );
			}
		}

		if ( ! $url ) {
			$url = home_url( '/' );
		}

		$tab = sanitize_key( (string) $tab );
		if ( '' !== $tab && array_key_exists( $tab, self::tab_choices() ) ) {
			$url = add_query_arg( 'tab', $tab, $url );
		}

		return $url;
	}

	/**
	 * Redirige usuarios operativos fuera del escritorio de WordPress.
	 *
	 * @return void
	 */
	public static function maybe_redirect_wp_admin() {
		if ( ! is_user_logged_in() || wp_doing_ajax() ) {
			return;
		}

		if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
			return;
		}

		if ( ! is_admin() ) {
			return;
		}

		if ( current_user_can( 'manage_options' ) ) {
			return;
		}

		if ( ! self::user_can_access_plugin() ) {
			return;
		}

		global $pagenow;

		$allowed = array(
			'admin-ajax.php',
			'admin-post.php',
		);

		if ( in_array( $pagenow, $allowed, true ) ) {
			return;
		}

		if ( 'admin.php' === $pagenow && isset( $_GET['page'] ) && 'logistica' === sanitize_key( wp_unslash( $_GET['page'] ) ) ) {
			require_once LOGISTICA_PLUGIN_DIR . 'admin/views/partials.php';
			wp_safe_redirect( Logistica_Roles::get_operations_panel_url( logistica_get_current_tab() ) );
			exit;
		}

		wp_safe_redirect( self::get_operations_panel_url() );
		exit;
	}

	/**
	 * @param bool $show Whether to show admin bar.
	 * @return bool
	 */
	public static function filter_admin_bar( $show ) {
		if ( ! is_user_logged_in() || current_user_can( 'manage_options' ) ) {
			return $show;
		}

		if ( self::user_can_access_plugin() ) {
			return false;
		}

		return $show;
	}

	/**
	 * @param string           $redirect_to Redirect URL.
	 * @param string           $request     Requested redirect.
	 * @param WP_User|WP_Error $user        User object.
	 * @return string
	 */
	public static function login_redirect( $redirect_to, $request, $user ) {
		if ( ! $user instanceof WP_User ) {
			return $redirect_to;
		}

		if ( user_can( $user, 'manage_options' ) ) {
			return $redirect_to;
		}

		if ( self::user_can_access_plugin( $user->ID ) ) {
			return self::get_operations_panel_url( 'logistica' );
		}

		return $redirect_to;
	}

	/**
	 * @param WP_User              $user User being edited.
	 * @param array<string, mixed> $args layout: table|cards.
	 * @return void
	 */
	public static function render_user_profile_fields( $user, $args = array() ) {
		if ( ! current_user_can( 'manage_options' ) && ! self::user_can_manage_config() ) {
			return;
		}

		$layout      = isset( $args['layout'] ) ? sanitize_key( (string) $args['layout'] ) : 'table';
		$definitions = self::get_role_definitions();
		$assigned    = get_user_meta( $user->ID, self::USER_META_ROLE, true );
		$overrides   = get_user_meta( $user->ID, self::USER_META_TABS, true );
		if ( ! is_array( $overrides ) ) {
			$overrides = array();
		}
		$tab_choices = self::tab_choices();
		unset( $tab_choices['logistica-configuracion'] );

		if ( 'cards' === $layout ) {
			self::render_user_profile_fields_cards( $definitions, $assigned, $overrides, $tab_choices );
			return;
		}
		?>
		<h2><?php esc_html_e( 'JB Planeación', 'logistica' ); ?></h2>
		<table class="form-table" role="presentation">
			<tr>
				<th><label for="logistica-jb-role"><?php esc_html_e( 'Rol operativo', 'logistica' ); ?></label></th>
				<td>
					<select name="logistica_jb_role" id="logistica-jb-role">
						<option value=""><?php esc_html_e( '— Sin rol JB —', 'logistica' ); ?></option>
						<?php foreach ( $definitions as $role_slug => $role_data ) : ?>
							<option value="<?php echo esc_attr( $role_slug ); ?>" <?php selected( $assigned, $role_slug ); ?>>
								<?php echo esc_html( $role_data['label'] ); ?>
							</option>
						<?php endforeach; ?>
					</select>
					<p class="description"><?php esc_html_e( 'Define el área operativa del usuario dentro de la empresa gráfica.', 'logistica' ); ?></p>
				</td>
			</tr>
			<tr>
				<th scope="row"><?php esc_html_e( 'Pestañas personalizadas', 'logistica' ); ?></th>
				<td>
					<p class="description"><?php esc_html_e( 'Opcional: reemplaza las pestañas del rol solo para este usuario. Deja vacío para usar el rol.', 'logistica' ); ?></p>
					<?php foreach ( array( 'backend' => __( 'Backend (operaciones)', 'logistica' ), 'frontend' => __( 'Frontend (panel público)', 'logistica' ) ) as $ctx => $ctx_label ) : ?>
						<fieldset style="margin: 1em 0;">
							<legend><strong><?php echo esc_html( $ctx_label ); ?></strong></legend>
							<?php
							$selected = isset( $overrides[ $ctx ] ) && is_array( $overrides[ $ctx ] ) ? $overrides[ $ctx ] : array();
							foreach ( $tab_choices as $slug => $label ) :
								?>
								<label style="display:block;margin:.25em 0;">
									<input type="checkbox" name="logistica_user_tabs[<?php echo esc_attr( $ctx ); ?>][]" value="<?php echo esc_attr( $slug ); ?>" <?php checked( in_array( $slug, $selected, true ) ); ?> />
									<?php echo esc_html( $label ); ?>
								</label>
							<?php endforeach; ?>
						</fieldset>
					<?php endforeach; ?>
				</td>
			</tr>
		</table>
		<?php
	}

	/**
	 * Icono dashicons por rol operativo.
	 *
	 * @param string $role_slug Role slug.
	 * @return string
	 */
	private static function role_icon_class( $role_slug ) {
		$icons = array(
			'jb_administrador' => 'dashicons-admin-generic',
			'jb_comercial'     => 'dashicons-businessman',
			'jb_coordinador'   => 'dashicons-clipboard',
			'jb_calidad'       => 'dashicons-yes-alt',
			'jb_produccion'    => 'dashicons-hammer',
			'jb_logistica'     => 'dashicons-archive',
			'jb_colaborador'   => 'dashicons-groups',
		);

		return $icons[ $role_slug ] ?? 'dashicons-admin-users';
	}

	/**
	 * Campos JB con tarjetas de rol (standalone).
	 *
	 * @param array<string, array{label: string, description: string}> $definitions Role definitions.
	 * @param string                                                 $assigned    Assigned role.
	 * @param array<string, list<string>>                            $overrides   Tab overrides.
	 * @param array<string, string>                                  $tab_choices Tab labels.
	 * @return void
	 */
	private static function render_user_profile_fields_cards( $definitions, $assigned, $overrides, $tab_choices ) {
		?>
		<div class="logistica-user-form__section">
			<div class="logistica-user-form__section-head">
				<h2><?php esc_html_e( 'Rol operativo', 'logistica' ); ?></h2>
				<p><?php esc_html_e( 'Elige el área de trabajo del usuario dentro de JB Planeación.', 'logistica' ); ?></p>
			</div>

			<div class="logistica-user-form__role-grid" data-logistica-role-picker>
				<label class="logistica-user-form__role-card<?php echo '' === $assigned ? ' is-selected' : ''; ?>">
					<input type="radio" name="logistica_jb_role" value="" <?php checked( $assigned, '' ); ?>>
					<span class="logistica-user-form__role-icon" aria-hidden="true"><span class="dashicons dashicons-minus"></span></span>
					<span class="logistica-user-form__role-copy">
						<strong><?php esc_html_e( 'Sin rol JB', 'logistica' ); ?></strong>
						<span><?php esc_html_e( 'Usuario creado sin acceso operativo.', 'logistica' ); ?></span>
					</span>
				</label>
				<?php foreach ( $definitions as $role_slug => $role_data ) : ?>
					<label class="logistica-user-form__role-card<?php echo $assigned === $role_slug ? ' is-selected' : ''; ?>">
						<input type="radio" name="logistica_jb_role" value="<?php echo esc_attr( $role_slug ); ?>" <?php checked( $assigned, $role_slug ); ?>>
						<span class="logistica-user-form__role-icon" aria-hidden="true"><span class="dashicons <?php echo esc_attr( self::role_icon_class( $role_slug ) ); ?>"></span></span>
						<span class="logistica-user-form__role-copy">
							<strong><?php echo esc_html( $role_data['label'] ); ?></strong>
							<span><?php echo esc_html( $role_data['description'] ); ?></span>
						</span>
					</label>
				<?php endforeach; ?>
			</div>
		</div>

		<details class="logistica-user-form__details">
			<summary>
				<span class="dashicons dashicons-admin-settings" aria-hidden="true"></span>
				<?php esc_html_e( 'Pestañas personalizadas', 'logistica' ); ?>
				<em><?php esc_html_e( 'Opcional', 'logistica' ); ?></em>
			</summary>
			<div class="logistica-user-form__details-body">
				<p class="logistica-user-form__details-intro"><?php esc_html_e( 'Sobrescribe las pestañas del rol solo para este usuario. Déjalo vacío para usar las del rol.', 'logistica' ); ?></p>
				<div class="logistica-user-form__tabs-grid">
					<?php foreach ( array( 'backend' => __( 'Panel operativo', 'logistica' ), 'frontend' => __( 'Panel público', 'logistica' ) ) as $ctx => $ctx_label ) : ?>
						<fieldset class="logistica-user-form__tabs-group">
							<legend><?php echo esc_html( $ctx_label ); ?></legend>
							<div class="logistica-user-form__tabs-list">
								<?php
								$selected = isset( $overrides[ $ctx ] ) && is_array( $overrides[ $ctx ] ) ? $overrides[ $ctx ] : array();
								foreach ( $tab_choices as $slug => $label ) :
									?>
									<label class="logistica-user-form__tab-item">
										<input type="checkbox" name="logistica_user_tabs[<?php echo esc_attr( $ctx ); ?>][]" value="<?php echo esc_attr( $slug ); ?>" <?php checked( in_array( $slug, $selected, true ) ); ?> />
										<span><?php echo esc_html( $label ); ?></span>
									</label>
								<?php endforeach; ?>
							</div>
						</fieldset>
					<?php endforeach; ?>
				</div>
			</div>
		</details>
		<?php
	}

	/**
	 * @param int $user_id User ID.
	 * @return void
	 */
	public static function save_user_profile_fields( $user_id ) {
		if ( ! current_user_can( 'manage_options' ) && ! self::user_can_manage_config() ) {
			return;
		}

		if ( ! current_user_can( 'edit_user', $user_id ) ) {
			return;
		}

		$user = get_userdata( $user_id );
		if ( ! $user ) {
			return;
		}

		$role_slug   = isset( $_POST['logistica_jb_role'] ) ? sanitize_key( wp_unslash( (string) $_POST['logistica_jb_role'] ) ) : '';
		$definitions = self::get_role_definitions();
		$current_jb  = self::get_user_jb_roles( $user );

		foreach ( array_keys( $definitions ) as $jb_role ) {
			$user->remove_role( $jb_role );
		}

		if ( '' !== $role_slug && isset( $definitions[ $role_slug ] ) ) {
			$user->add_role( $role_slug );
			update_user_meta( $user_id, self::USER_META_ROLE, $role_slug );
		} else {
			delete_user_meta( $user_id, self::USER_META_ROLE );
		}

		$raw_overrides = isset( $_POST['logistica_user_tabs'] ) && is_array( $_POST['logistica_user_tabs'] )
			? wp_unslash( $_POST['logistica_user_tabs'] )
			: array();

		$overrides = array();
		foreach ( array( 'backend', 'frontend' ) as $ctx ) {
			$list = self::sanitize_tab_list( $raw_overrides[ $ctx ] ?? array() );
			if ( ! empty( $list ) ) {
				$overrides[ $ctx ] = $list;
			}
		}

		if ( empty( $overrides ) ) {
			delete_user_meta( $user_id, self::USER_META_TABS );
		} else {
			update_user_meta( $user_id, self::USER_META_TABS, $overrides );
		}
	}

	/**
	 * @param int|null $user_id User ID.
	 * @return WP_User|null
	 */
	private static function resolve_user( $user_id = null ) {
		if ( null === $user_id ) {
			$user = wp_get_current_user();
			return $user && $user->exists() ? $user : null;
		}

		$user = get_userdata( (int) $user_id );
		return $user instanceof WP_User ? $user : null;
	}
}
