import 'package:flutter/material.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:provider/provider.dart'; import '../estado/estado_juego.dart'; import '../modelos/inicio_partida_multijugador.dart'; import '../modelos/palabra.dart'; import '../modelos/partida.dart'; import '../modelos/usuario.dart'; import '../servicios/servicio_nearby.dart'; import '../servicios/servicio_permisos.dart'; import '../tema/tema_app.dart'; import 'pantalla_gestor_host.dart'; import 'pantalla_lobby_host.dart'; import 'pantalla_principal.dart'; import 'pantalla_ver_palabra.dart'; class PantallaCrearPartida extends StatefulWidget { const PantallaCrearPartida({super.key}); @override State createState() => _PantallaCrearPartidaState(); } class _PantallaCrearPartidaState extends State { bool _modoMultimovil = false; String _categoria = 'todas'; int _numImpostores = 1; bool _pistaImpostor = false; int? _tiempoDebate; final List _jugadores = []; final _controladorNombre = TextEditingController(); final _opcionesTiempo = [null, 60, 120, 180, 300]; int get _maxImpostores => (_jugadores.length / 3).floor().clamp(1, 4); List _etiquetasTiempo(AppLocalizations l10n) => [ l10n.noLimit, l10n.oneMin, l10n.twoMin, l10n.threeMin, l10n.fiveMin, ]; void _agregarJugador() { final l10n = AppLocalizations.of(context)!; final nombre = _controladorNombre.text.trim(); if (nombre.isEmpty) return; if (_jugadores.contains(nombre)) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(l10n.playerAlreadyExists))); return; } if (_jugadores.length >= 20) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(l10n.maxPlayersReached))); return; } setState(() { _jugadores.add(nombre); _controladorNombre.clear(); if (_numImpostores > _maxImpostores) { _numImpostores = _maxImpostores; } }); } void _eliminarJugador(int index) { setState(() { _jugadores.removeAt(index); if (_numImpostores > _maxImpostores && _maxImpostores > 0) { _numImpostores = _maxImpostores; } }); } void _iniciarPartida() { final l10n = AppLocalizations.of(context)!; if (_modoMultimovil) { _iniciarPartidaMulti(); return; } if (_jugadores.length < 3) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(l10n.minPlayersRequired))); return; } final estado = context.read(); estado.crearPartida( config: ConfigPartida( modoMultimovil: false, categoria: _categoria, numImpostores: _numImpostores, pistaImpostor: _pistaImpostor, tiempoDebateSegundos: _tiempoDebate, ), nombresJugadores: _jugadores, ); Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const PantallaVerPalabra()), ); } Future _iniciarPartidaMulti() async { // 1. Pedir permisos automáticamente final permisosOk = await ServicioPermisos.solicitarPermisosNearby(context); if (!permisosOk) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Se necesitan permisos de Bluetooth y ubicación'), ), ); } return; } // 2. Seleccionar o crear usuario del pool final nombre = await _seleccionarUsuarioHost(); if (nombre == null || nombre.trim().isEmpty) return; // 3. Iniciar host en Nearby if (!mounted) return; final nearby = context.read(); final nombreSala = '${nombre.trim()} - Farolero'; final ok = await nearby.iniciarHost(nombreSala, nombre.trim()); if (!ok) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('No se pudo crear la sala. Verifica Bluetooth.'), ), ); } return; } // 4. Navegar al lobby con QR if (mounted) { Navigator.push( context, MaterialPageRoute( builder: (_) => PantallaLobbyHost( nombreSala: nombreSala, onIniciar: () { // Cuando el host toca "Iniciar" con suficientes jugadores final estado = context.read(); final sala = nearby.estadoSala; if (sala == null) return; final validacion = sala.iniciarPartida(); if (!validacion.exitoso) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'No se puede iniciar: ${validacion.codigo ?? "sala inválida"}', ), ), ); return; } estado.crearPartidaDesdeSala( config: ConfigPartida( modoMultimovil: true, categoria: _categoria, numImpostores: _numImpostores, pistaImpostor: _pistaImpostor, tiempoDebateSegundos: _tiempoDebate, ), sala: sala, ); final partida = estado.partida!; final asignaciones = partida.jugadores.map((jugador) { final usuarioSala = sala.usuarios[jugador.id]; final clientId = usuarioSala?.clienteIdSeleccionado; final cliente = clientId == null ? null : sala.clientes[clientId]; return AsignacionJugador( jugadorId: jugador.id, nombre: jugador.nombre, clientId: clientId ?? sala.hostClientId, endpointId: cliente?.endpointId, ); }).toList(); final impostores = { for (final jugador in partida.jugadores) jugador.id: jugador.esImpostor, }; final jugadoresTodos = partida.jugadores .map( (jugador) => { 'id': jugador.id, 'nombre': jugador.nombre, 'eliminado': jugador.eliminado, }, ) .toList(); nearby.enviarInicioPartidaMulti( asignaciones: asignaciones, palabraSecreta: partida.palabraSecreta, categoria: _categoria, impostoresPorJugadorId: impostores, jugadoresTodos: jugadoresTodos, ); Navigator.pushReplacement( context, MaterialPageRoute( builder: (_) => PantallaGestorHost( onPartidaFin: () { estado.limpiar(); Navigator.pushReplacement( context, MaterialPageRoute( builder: (_) => const PantallaPrincipal(), ), ); }, ), ), ); }, ), ), ); } } /// Muestra diálogo para seleccionar usuario del pool o crear nuevo Future _seleccionarUsuarioHost() async { final l10n = AppLocalizations.of(context)!; final nearby = context.read(); final usuarios = nearby.usuarios; // Si hay usuarios en el pool, mostrar selección if (usuarios.isNotEmpty) { String? seleccionado; return showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setDialogState) => AlertDialog( title: Text(l10n.selectYourProfile), content: Column( mainAxisSize: MainAxisSize.min, children: [ DropdownButtonFormField( initialValue: seleccionado, decoration: InputDecoration( prefixIcon: const Icon(Icons.person), hintText: l10n.selectProfile, border: const OutlineInputBorder(), ), items: [ // Opción para crear nuevo usuario DropdownMenuItem( value: '_new_', child: Row( children: [ const Icon(Icons.add, size: 18), const SizedBox(width: 8), Text(l10n.createNewUser), ], ), ), // Usuarios existentes ...usuarios.map((usuario) { return DropdownMenuItem( value: usuario.nombre, child: Row( children: [ Text(usuario.avatar ?? '👤'), const SizedBox(width: 8), Text(usuario.nombre), ], ), ); }), ], onChanged: (valor) { setDialogState(() => seleccionado = valor); }, ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: Text(l10n.cancel), ), TextButton( onPressed: () { if (seleccionado == '_new_') { Navigator.pop(ctx); _crearNuevoUsuarioHost(); } else if (seleccionado != null) { Navigator.pop(ctx, seleccionado); } }, child: Text(l10n.accept), ), ], ), ), ); } // Pool vacío, pedir nombre nuevo return _pedirNombreHost(); } /// Crea un nuevo usuario y lo agrega al pool Future _crearNuevoUsuarioHost() async { final controller = TextEditingController(); final l10n = AppLocalizations.of(context)!; final nearby = context.read(); final nombre = await showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(l10n.createNewUser), content: TextField( controller: controller, autofocus: true, textCapitalization: TextCapitalization.words, decoration: InputDecoration( hintText: l10n.yourName, prefixIcon: const Icon(Icons.person), ), onSubmitted: (v) => Navigator.pop(ctx, v), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: Text(l10n.cancel), ), TextButton( onPressed: () => Navigator.pop(ctx, controller.text), child: const Text('OK'), ), ], ), ); if (nombre != null && nombre.trim().isNotEmpty) { final nuevoUsuario = Usuario( id: DateTime.now().millisecondsSinceEpoch.toString(), nombre: nombre.trim(), ); nearby.agregarUsuario(nuevoUsuario); return nombre.trim(); } return null; } /// Método original para pedir nombre (usado cuando pool vacío) Future _pedirNombreHost() async { final controller = TextEditingController(); final l10n = AppLocalizations.of(context)!; return showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(l10n.yourName), content: TextField( controller: controller, autofocus: true, textCapitalization: TextCapitalization.words, decoration: InputDecoration( hintText: l10n.yourName, prefixIcon: const Icon(Icons.person), ), onSubmitted: (v) => Navigator.pop(ctx, v), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: Text(l10n.cancel), ), TextButton( onPressed: () => Navigator.pop(ctx, controller.text), child: const Text('OK'), ), ], ), ); } @override void dispose() { _controladorNombre.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final estado = context.watch(); final categorias = ['todas', ...?estado.banco?.nombresCategorias]; final etiquetas = _etiquetasTiempo(l10n); return Scaffold( appBar: AppBar(title: Text(l10n.createGame)), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Modo de juego Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.gameMode, style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), SegmentedButton( segments: [ ButtonSegment( value: false, label: Text(l10n.singleDevice), icon: const Icon(Icons.phone_android), ), ButtonSegment( value: true, label: Text(l10n.multiDevice), icon: const Icon(Icons.devices), ), ], selected: {_modoMultimovil}, onSelectionChanged: (valor) { setState(() => _modoMultimovil = valor.first); }, ), ], ), ), ), const SizedBox(height: 12), // Categoría Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.category, style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: DropdownButtonFormField( initialValue: _categoria, decoration: const InputDecoration( prefixIcon: Icon(Icons.category), ), items: categorias.map((c) { return DropdownMenuItem( value: c, child: Text( BancoPalabras.nombreBonitoCategoria(c, l10n), ), ); }).toList(), onChanged: (v) => setState(() => _categoria = v!), ), ), ], ), ), ), const SizedBox(height: 12), // Jugadores Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( l10n.playersCount(_jugadores.length), style: Theme.of(context).textTheme.titleLarge, ), Text( l10n.playersRangeHint, style: Theme.of(context).textTheme.bodyMedium, ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: TextField( controller: _controladorNombre, decoration: InputDecoration( hintText: l10n.playerNameHint, prefixIcon: const Icon(Icons.person_add), ), textCapitalization: TextCapitalization.words, onSubmitted: (_) => _agregarJugador(), ), ), const SizedBox(width: 8), IconButton.filled( onPressed: _agregarJugador, icon: const Icon(Icons.add), ), ], ), const SizedBox(height: 8), ..._jugadores.asMap().entries.map((e) { return ListTile( leading: CircleAvatar( backgroundColor: TemaApp.colorTarjeta, child: Text( '${e.key + 1}', style: const TextStyle(color: TemaApp.colorTexto), ), ), title: Text(e.value), trailing: IconButton( icon: const Icon( Icons.close, color: TemaApp.colorAcento, ), onPressed: () => _eliminarJugador(e.key), ), dense: true, ); }), ], ), ), ), const SizedBox(height: 12), // Configuración de partida Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.configuration, style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), // Número de impostores Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(l10n.impostors), Row( children: [ IconButton( onPressed: _numImpostores > 1 ? () => setState(() => _numImpostores--) : null, icon: const Icon(Icons.remove_circle_outline), ), Text( '$_numImpostores', style: Theme.of(context).textTheme.titleLarge, ), IconButton( onPressed: _numImpostores < _maxImpostores ? () => setState(() => _numImpostores++) : null, icon: const Icon(Icons.add_circle_outline), ), ], ), ], ), // Pista para impostor SwitchListTile( title: Text(l10n.impostorClue), subtitle: Text(l10n.impostorClueDescription), value: _pistaImpostor, onChanged: (v) => setState(() => _pistaImpostor = v), contentPadding: EdgeInsets.zero, ), // Temporizador Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(l10n.debateTime), DropdownButton( value: _tiempoDebate, items: List.generate( _opcionesTiempo.length, (i) => DropdownMenuItem( value: _opcionesTiempo[i], child: Text(etiquetas[i]), ), ), onChanged: (v) => setState(() => _tiempoDebate = v), ), ], ), ], ), ), ), const SizedBox(height: 24), // Botón iniciar SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: (_modoMultimovil || _jugadores.length >= 3) ? _iniciarPartida : null, icon: const Icon(Icons.play_arrow), label: Text(l10n.startGame), style: ElevatedButton.styleFrom( textStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(height: 16), ], ), ), ); } }