import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:nearby_connections/nearby_connections.dart';
import '../modelos/inicio_partida_multijugador.dart';
import '../modelos/sala_multijugador.dart';
import '../modelos/usuario.dart';

/// Tipos de mensajes en el protocolo P2P.
enum TipoMensaje {
  salaInfo,
  partidaInicio,
  fase,
  votacionResultado,
  partidaFin,
  unirse,
  voto,
  listo,
  ping,
  jugadorDesconectado,
  clienteRegistrado,
  estadoSala,
  crearUsuario,
  seleccionarUsuario,
  liberarUsuario,
  eliminarUsuario,
  errorOperacion,
  usuarioNuevo,
  // Compatibilidad con versiones previas del protocolo.
  usuarioEliminado,
  usuariosActualizados,
}

/// Mensaje del protocolo P2P entre dispositivos.
class MensajeP2P {
  final TipoMensaje tipo;
  final Map<String, dynamic> datos;

  MensajeP2P({required this.tipo, required this.datos});

  String toJson() => json.encode({'tipo': tipo.name, 'datos': datos});

  factory MensajeP2P.fromJson(String jsonStr) {
    final mapa = json.decode(jsonStr) as Map<String, dynamic>;
    return MensajeP2P(
      tipo: TipoMensaje.values.firstWhere((t) => t.name == mapa['tipo']),
      datos: mapa['datos'] as Map<String, dynamic>,
    );
  }

  Uint8List toBytes() => Uint8List.fromList(utf8.encode(toJson()));
}

/// Info de un dispositivo conectado. El nombre identifica al cliente/dispositivo,
/// no necesariamente a un jugador de la partida.
class JugadorConectado {
  final String endpointId;
  final String nombre;
  bool listo;

  JugadorConectado({
    required this.endpointId,
    required this.nombre,
    this.listo = false,
  });
}

typedef OnMensajeCallback =
    void Function(String endpointId, MensajeP2P mensaje);

/// Servicio para conexiones P2P usando Google Nearby Connections API.
class ServicioNearby extends ChangeNotifier {
  static const _serviceId = 'es.freetimelab.farolero';
  static const _hostClientId = 'host';

  bool _esHost = false;
  bool _conectado = false;
  bool _buscando = false;
  bool _anunciando = false;
  String? _miEndpointId;
  String? _hostEndpointId;
  String? _roomId;
  String? _miClientId;
  String? _nombreSala;
  String? _miNombre;
  String? _miNick;
  String? _miAvatar;
  int _miFuego = 0;
  List<String> _miMedallas = const [];

  final Map<String, JugadorConectado> _jugadores = {};
  final List<OnMensajeCallback> _listeners = [];
  final Map<String, String> _hostsEncontrados = {};
  final Map<String, Usuario> _usuariosPool = {};
  Timer? _heartbeatTimer;

  String? _palabraRecibida;
  bool? _soyImpostor;
  String? _faseActual;
  Map<String, dynamic>? _datosPartida;
  EstadoSalaMultijugador? _estadoSala;

  bool get esHost => _esHost;
  bool get conectado => _conectado;
  bool get buscando => _buscando;
  bool get anunciando => _anunciando;
  String? get miEndpointId => _miEndpointId;
  String? get hostEndpointId => _hostEndpointId;
  String? get roomId => _roomId;
  String? get miClientId => _miClientId;
  String? get nombreSala => _nombreSala;
  String? get miNombre => _miNombre;
  String? get palabraRecibida => _palabraRecibida;
  bool? get soyImpostor => _soyImpostor;
  String? get faseActual => _faseActual;
  Map<String, dynamic>? get datosPartida => _datosPartida;
  EstadoSalaMultijugador? get estadoSala => _estadoSala;

  List<JugadorConectado> get jugadores => _jugadores.values.toList();
  int get numJugadoresConectados => _jugadores.length;
  Map<String, String> get hostsEncontrados =>
      Map.unmodifiable(_hostsEncontrados);

  List<Usuario> get usuarios =>
      (_estadoSala?.usuarios.values ?? _usuariosPool.values).toList();

  List<Usuario> get misUsuariosSeleccionados {
    final clientId = _miClientId;
    final sala = _estadoSala;
    if (clientId == null || sala == null) return [];
    return sala.usuariosPorCliente(clientId);
  }

  void onMensaje(OnMensajeCallback callback) {
    _listeners.add(callback);
  }

  void removeMensajeListener(OnMensajeCallback callback) {
    _listeners.remove(callback);
  }

  void _notificarMensaje(String endpointId, MensajeP2P mensaje) {
    for (final listener in _listeners) {
      listener(endpointId, mensaje);
    }
  }

  // ==================== USER POOL / SALA ====================

  void agregarUsuario(Usuario usuario) {
    _usuariosPool[usuario.id] = usuario;
    _estadoSala?.usuarios[usuario.id] = usuario;
    notifyListeners();
  }

  void eliminarUsuario(String usuarioId) {
    _usuariosPool.remove(usuarioId);
    _estadoSala?.usuarios.remove(usuarioId);
    notifyListeners();
  }

  Usuario? getUsuario(String usuarioId) {
    return _estadoSala?.usuarios[usuarioId] ?? _usuariosPool[usuarioId];
  }

  void sincronizarUsuarios(List<Usuario> usuarios) {
    _usuariosPool.clear();
    for (final usuario in usuarios) {
      _usuariosPool[usuario.id] = usuario;
    }
    notifyListeners();
  }

  void _sincronizarSala(EstadoSalaMultijugador sala) {
    _estadoSala = sala;
    _roomId = sala.roomId;
    _usuariosPool
      ..clear()
      ..addEntries(sala.usuarios.entries);
  }

  void _sincronizarPoolDesdeSala() {
    final sala = _estadoSala;
    if (sala == null) return;
    _usuariosPool
      ..clear()
      ..addEntries(sala.usuarios.entries);
  }

  Future<void> _broadcastEstadoSala() async {
    final sala = _estadoSala;
    if (sala == null) return;
    _sincronizarPoolDesdeSala();
    if (_esHost) {
      await enviarATodos(
        MensajeP2P(tipo: TipoMensaje.estadoSala, datos: sala.toJson()),
      );
    }
    notifyListeners();
  }

  JugadorConectado? getJugadorLocal() {
    if (_miNombre == null) return null;
    return JugadorConectado(
      endpointId: _miEndpointId ?? '',
      nombre: _miNombre!,
      listo: true,
    );
  }

  // ==================== HOST ====================

  Future<bool> iniciarHost(
    String nombreSala,
    String miNombre, {
    String? miNick,
    String? miAvatar,
    int miFuego = 0,
    List<String> miMedallas = const [],
  }) async {
    if (_conectado ||
        _anunciando ||
        _buscando ||
        _estadoSala != null ||
        _jugadores.isNotEmpty) {
      await desconectar();
      await Future<void>.delayed(const Duration(milliseconds: 250));
    }

    _nombreSala = nombreSala;
    _miNombre = miNombre;
    _roomId = DateTime.now().microsecondsSinceEpoch.toString();
    _miClientId = _hostClientId;
    _estadoSala = EstadoSalaMultijugador.crear(
      roomId: _roomId!,
      nombreSala: nombreSala,
      hostClientId: _hostClientId,
      hostNombre: miNombre,
    );

    // Compatibilidad con el flujo actual: el nombre con el que se crea la sala
    // arranca como usuario seleccionado por el host. Luego puede crear/seleccionar
    // más usuarios en el lobby.
    final usuarioHost = Usuario(
      id: 'u-${_roomId!}-host',
      nombre: miNombre,
      nick: miNick,
      avatar: miAvatar,
      foto: miAvatar,
      creadoPorClienteId: _hostClientId,
      clienteIdSeleccionado: _hostClientId,
      fuego: miFuego,
      medallas: miMedallas,
    );
    _estadoSala!.crearUsuario(usuarioHost);
    _sincronizarPoolDesdeSala();

    try {
      final resultado = await Nearby().startAdvertising(
        miNombre,
        Strategy.P2P_STAR,
        onConnectionInitiated: _onConexionIniciada,
        onConnectionResult: _onResultadoConexion,
        onDisconnected: _onDesconexion,
        serviceId: _serviceId,
      );

      if (resultado) {
        _esHost = true;
        _anunciando = true;
        _conectado = true;
        notifyListeners();
        return true;
      }
      await desconectar();
      return false;
    } catch (e) {
      debugPrint('Error iniciando host: $e');
      await desconectar();
      return false;
    }
  }

  // ==================== CLIENTE ====================

  Future<bool> buscarHosts(
    String miNombre, {
    String? miNick,
    String? miAvatar,
    int miFuego = 0,
    List<String> miMedallas = const [],
  }) async {
    _miNombre = miNombre;
    _miNick = miNick;
    _miAvatar = miAvatar;
    _miFuego = miFuego;
    _miMedallas = miMedallas;

    try {
      final resultado = await Nearby().startDiscovery(
        miNombre,
        Strategy.P2P_STAR,
        onEndpointFound: _onEndpointEncontrado,
        onEndpointLost: _onEndpointPerdido,
        serviceId: _serviceId,
      );

      if (resultado) {
        _buscando = true;
        notifyListeners();
        return true;
      }
      return false;
    } catch (e) {
      debugPrint('Error buscando hosts: $e');
      return false;
    }
  }

  Future<bool> conectarAHost(
    String endpointId,
    String miNombre, {
    String? miNick,
    String? miAvatar,
    int miFuego = 0,
    List<String> miMedallas = const [],
  }) async {
    _miNombre = miNombre;
    _miNick = miNick;
    _miAvatar = miAvatar;
    _miFuego = miFuego;
    _miMedallas = miMedallas;
    try {
      await Nearby().requestConnection(
        miNombre,
        endpointId,
        onConnectionInitiated: _onConexionIniciada,
        onConnectionResult: _onResultadoConexion,
        onDisconnected: _onDesconexion,
      );
      return true;
    } catch (e) {
      debugPrint('Error conectando a host: $e');
      return false;
    }
  }

  // ==================== CALLBACKS NEARBY ====================

  void _onConexionIniciada(String endpointId, ConnectionInfo info) {
    debugPrint('Conexion iniciada con $endpointId: ${info.endpointName}');
    Nearby().acceptConnection(
      endpointId,
      onPayLoadRecieved: _onPayloadRecibido,
      onPayloadTransferUpdate: _onPayloadUpdate,
    );
  }

  void _onResultadoConexion(String endpointId, Status status) {
    debugPrint('Resultado conexion $endpointId: $status');
    if (status == Status.CONNECTED) {
      if (_esHost) {
        debugPrint('Cliente conectado: $endpointId');
      } else {
        _hostEndpointId = endpointId;
        _conectado = true;
        _iniciarHeartbeatCliente();
        enviarMensaje(
          endpointId,
          MensajeP2P(
            tipo: TipoMensaje.unirse,
            datos: {
              'nombre': _miNombre ?? 'Jugador',
              if (_miNick != null) 'nick': _miNick,
              if (_miAvatar != null) 'avatar': _miAvatar,
              'fuego': _miFuego,
              'medallas': _miMedallas,
            },
          ),
        );
      }
      notifyListeners();
    } else {
      debugPrint('Conexion fallida con $endpointId');
    }
  }

  void _onDesconexion(String endpointId) {
    debugPrint('Desconexion: $endpointId');
    if (_esHost) {
      final jugador = _jugadores.remove(endpointId);
      final cliente = _estadoSala?.clientePorEndpoint(endpointId);
      if (cliente != null) {
        _estadoSala?.desconectarCliente(cliente.clientId);
        _broadcastEstadoSala();
      }
      if (jugador != null) {
        enviarATodos(
          MensajeP2P(
            tipo: TipoMensaje.jugadorDesconectado,
            datos: {'nombre': jugador.nombre, 'endpointId': endpointId},
          ),
        );
      }
    } else {
      _conectado = false;
      _hostEndpointId = null;
      _heartbeatTimer?.cancel();
    }
    notifyListeners();
  }


  void _iniciarHeartbeatCliente() {
    _heartbeatTimer?.cancel();
    _heartbeatTimer = Timer.periodic(const Duration(seconds: 5), (_) {
      final hostId = _hostEndpointId;
      if (!_esHost && _conectado && hostId != null) {
        enviarMensaje(
          hostId,
          MensajeP2P(
            tipo: TipoMensaje.ping,
            datos: {
              'heartbeat': true,
              if (_miClientId != null) 'clientId': _miClientId,
            },
          ),
        );
      }
    });
  }
  void _onEndpointEncontrado(
    String endpointId,
    String endpointName,
    String serviceId,
  ) {
    debugPrint('Host encontrado: $endpointName ($endpointId)');
    _hostsEncontrados[endpointId] = endpointName;
    notifyListeners();
  }

  void _onEndpointPerdido(String? endpointId) {
    debugPrint('Endpoint perdido: $endpointId');
    if (endpointId != null) {
      _hostsEncontrados.remove(endpointId);
      notifyListeners();
    }
  }

  Future<void> pararBusqueda() async {
    try {
      await Nearby().stopDiscovery();
    } catch (_) {}
    _buscando = false;
    _hostsEncontrados.clear();
    notifyListeners();
  }

  void _onPayloadRecibido(String endpointId, Payload payload) {
    if (payload.type == PayloadType.BYTES && payload.bytes != null) {
      try {
        final jsonStr = utf8.decode(payload.bytes!);
        final mensaje = MensajeP2P.fromJson(jsonStr);
        _procesarMensaje(endpointId, mensaje);
      } catch (e) {
        debugPrint('Error procesando payload: $e');
      }
    }
  }

  void _onPayloadUpdate(String endpointId, PayloadTransferUpdate update) {}

  // ==================== PROCESAMIENTO DE MENSAJES ====================

  void _procesarMensaje(String endpointId, MensajeP2P mensaje) {
    debugPrint('Mensaje de $endpointId: ${mensaje.tipo.name}');

    if (_esHost) {
      _procesarMensajeHost(endpointId, mensaje);
    } else {
      _procesarMensajeCliente(endpointId, mensaje);
    }

    _notificarMensaje(endpointId, mensaje);
  }

  void _procesarMensajeHost(String endpointId, MensajeP2P mensaje) {
    final cliente = _estadoSala?.clientePorEndpoint(endpointId);
    if (cliente != null) {
      _estadoSala?.registrarActividadCliente(cliente.clientId);
    }
    switch (mensaje.tipo) {
      case TipoMensaje.unirse:
        _registrarClienteRemoto(endpointId, mensaje);
        break;
      case TipoMensaje.voto:
        _notificarMensaje(endpointId, mensaje);
        break;
      case TipoMensaje.listo:
        final jugador = _jugadores[endpointId];
        if (jugador != null) {
          jugador.listo = true;
          notifyListeners();
        }
        break;
      case TipoMensaje.crearUsuario:
        _handleCrearUsuario(endpointId, mensaje);
        break;
      case TipoMensaje.seleccionarUsuario:
        _handleSeleccionarUsuario(endpointId, mensaje);
        break;
      case TipoMensaje.liberarUsuario:
        _handleLiberarUsuario(endpointId, mensaje);
        break;
      case TipoMensaje.eliminarUsuario:
      case TipoMensaje.usuarioEliminado:
        _handleEliminarUsuario(endpointId, mensaje);
        break;
      case TipoMensaje.usuarioNuevo:
        _handleUsuarioNuevo(mensaje);
        break;
      case TipoMensaje.usuariosActualizados:
        _handleUsuariosActualizados(mensaje);
        break;
      default:
        break;
    }
  }

  void _registrarClienteRemoto(String endpointId, MensajeP2P mensaje) {
    final nombre = mensaje.datos['nombre'] as String? ?? 'Jugador';
    final nick = mensaje.datos['nick'] as String?;
    final avatar = mensaje.datos['avatar'] as String?;
    final fuego = (mensaje.datos['fuego'] as num?)?.toInt() ?? 0;
    final medallas = (mensaje.datos['medallas'] as List<dynamic>? ?? const [])
        .map((valor) => valor.toString())
        .toList();
    final clientId = endpointId;
    _jugadores[endpointId] = JugadorConectado(
      endpointId: endpointId,
      nombre: nombre,
    );
    _estadoSala?.registrarCliente(
      ClienteSala(clientId: clientId, endpointId: endpointId, nombre: nombre),
    );
    _crearUsuarioAutomaticoCliente(
      clientId: clientId,
      nombre: nombre,
      nick: nick,
      avatar: avatar,
      fuego: fuego,
      medallas: medallas,
    );

    enviarMensaje(
      endpointId,
      MensajeP2P(
        tipo: TipoMensaje.clienteRegistrado,
        datos: {
          'clientId': clientId,
          'sala': _nombreSala,
          'roomId': _roomId,
          'jugadores': _jugadores.values
              .map((j) => {'nombre': j.nombre, 'endpointId': j.endpointId})
              .toList(),
          'usuarios': _usuariosPool.values.map((u) => u.toJson()).toList(),
          if (_estadoSala != null) 'estadoSala': _estadoSala!.toJson(),
        },
      ),
    );
    _broadcastEstadoSala();
    notifyListeners();
  }

  void _crearUsuarioAutomaticoCliente({
    required String clientId,
    required String nombre,
    String? nick,
    String? avatar,
    int fuego = 0,
    List<String> medallas = const [],
  }) {
    final sala = _estadoSala;
    if (sala == null || sala.fase != FaseSalaMultijugador.lobby) return;

    final yaTieneUsuario = sala.usuariosPorCliente(clientId).isNotEmpty;
    if (yaTieneUsuario) return;

    final base = nombre.trim().isEmpty ? 'Jugador' : nombre.trim();
    var nombreFinal = base;
    var intento = 2;
    while (sala.usuarios.values.any(
      (u) => u.nombre.trim().toLowerCase() == nombreFinal.toLowerCase(),
    )) {
      nombreFinal = '$base ($intento)';
      intento++;
    }

    final usuario = Usuario(
      id: 'u-${_roomId ?? DateTime.now().microsecondsSinceEpoch}-$clientId',
      nombre: nombreFinal,
      nick: nick,
      avatar: avatar,
      foto: avatar,
      creadoPorClienteId: clientId,
      fuego: fuego,
      medallas: medallas,
    );
    final resultadoCrear = sala.crearUsuario(usuario);
    if (!resultadoCrear.exitoso) return;
    sala.seleccionarUsuario(usuarioId: usuario.id, clienteId: clientId);
    _sincronizarPoolDesdeSala();
  }

  void _handleUsuarioNuevo(MensajeP2P mensaje) {
    final usuarioJson = mensaje.datos['usuario'] as Map<String, dynamic>?;
    if (usuarioJson != null) {
      final nuevoUsuario = Usuario.fromJson(usuarioJson);
      _usuariosPool[nuevoUsuario.id] = nuevoUsuario;
      _estadoSala?.usuarios[nuevoUsuario.id] = nuevoUsuario;
      if (_esHost) {
        _broadcastEstadoSala();
      }
      notifyListeners();
    }
  }

  void _handleUsuariosActualizados(MensajeP2P mensaje) {
    final usuariosJson = mensaje.datos['usuarios'] as List<dynamic>?;
    if (usuariosJson != null) {
      _usuariosPool.clear();
      for (final u in usuariosJson) {
        final usuario = Usuario.fromJson(u as Map<String, dynamic>);
        _usuariosPool[usuario.id] = usuario;
      }
      notifyListeners();
    }
  }

  String _clientIdParaEndpoint(String endpointId, MensajeP2P mensaje) {
    return mensaje.datos['clientId'] as String? ??
        _estadoSala?.clientePorEndpoint(endpointId)?.clientId ??
        endpointId;
  }

  Future<void> _enviarErrorOperacion(
    String endpointId,
    ResultadoOperacionSala resultado,
  ) async {
    if (resultado.exitoso) return;
    await enviarMensaje(
      endpointId,
      MensajeP2P(tipo: TipoMensaje.errorOperacion, datos: resultado.toJson()),
    );
  }

  void _handleCrearUsuario(String endpointId, MensajeP2P mensaje) {
    final sala = _estadoSala;
    final usuarioJson = mensaje.datos['usuario'] as Map<String, dynamic>?;
    if (sala == null || usuarioJson == null) return;
    final clientId = _clientIdParaEndpoint(endpointId, mensaje);
    final usuario = Usuario.fromJson(usuarioJson).copiar(
      creadoPorClienteId: clientId,
      liberarSeleccion: true,
    );
    final resultadoCrear = sala.crearUsuario(usuario);
    if (!resultadoCrear.exitoso) {
      _enviarErrorOperacion(endpointId, resultadoCrear);
      return;
    }
    if (mensaje.datos['seleccionar'] == true) {
      final resultadoSeleccion = sala.seleccionarUsuario(
        usuarioId: usuario.id,
        clienteId: clientId,
      );
      _enviarErrorOperacion(endpointId, resultadoSeleccion);
    }
    _broadcastEstadoSala();
  }

  void _handleSeleccionarUsuario(String endpointId, MensajeP2P mensaje) {
    final sala = _estadoSala;
    final usuarioId = mensaje.datos['usuarioId'] as String?;
    if (sala == null || usuarioId == null) return;
    final resultado = sala.seleccionarUsuario(
      usuarioId: usuarioId,
      clienteId: _clientIdParaEndpoint(endpointId, mensaje),
    );
    _enviarErrorOperacion(endpointId, resultado);
    _broadcastEstadoSala();
  }

  void _handleLiberarUsuario(String endpointId, MensajeP2P mensaje) {
    final sala = _estadoSala;
    final usuarioId = mensaje.datos['usuarioId'] as String?;
    if (sala == null || usuarioId == null) return;
    final resultado = sala.liberarUsuario(
      usuarioId: usuarioId,
      solicitanteClientId: _clientIdParaEndpoint(endpointId, mensaje),
    );
    _enviarErrorOperacion(endpointId, resultado);
    _broadcastEstadoSala();
  }

  void _handleEliminarUsuario(String endpointId, MensajeP2P mensaje) {
    final sala = _estadoSala;
    final usuarioId = mensaje.datos['usuarioId'] as String?;
    if (sala == null || usuarioId == null) return;
    final resultado = sala.eliminarUsuario(
      usuarioId: usuarioId,
      solicitanteClientId: _clientIdParaEndpoint(endpointId, mensaje),
    );
    _enviarErrorOperacion(endpointId, resultado);
    _broadcastEstadoSala();
  }

  void _procesarMensajeCliente(String endpointId, MensajeP2P mensaje) {
    switch (mensaje.tipo) {
      case TipoMensaje.salaInfo:
        _datosPartida = mensaje.datos;
        final usuariosData = mensaje.datos['usuarios'] as List<dynamic>?;
        if (usuariosData != null) {
          _usuariosPool.clear();
          for (final u in usuariosData) {
            final usuario = Usuario.fromJson(u as Map<String, dynamic>);
            _usuariosPool[usuario.id] = usuario;
          }
        }
        notifyListeners();
        break;
      case TipoMensaje.clienteRegistrado:
        _miClientId = mensaje.datos['clientId'] as String?;
        _datosPartida = mensaje.datos;
        final estadoSalaJson =
            mensaje.datos['estadoSala'] as Map<String, dynamic>?;
        if (estadoSalaJson != null) {
          _sincronizarSala(EstadoSalaMultijugador.fromJson(estadoSalaJson));
        }
        notifyListeners();
        break;
      case TipoMensaje.estadoSala:
        _sincronizarSala(EstadoSalaMultijugador.fromJson(mensaje.datos));
        notifyListeners();
        break;
      case TipoMensaje.partidaInicio:
        final jugadoresInicio = mensaje.datos['jugadores'] as List<dynamic>?;
        if (jugadoresInicio != null && jugadoresInicio.isNotEmpty) {
          final primerJugador = jugadoresInicio.first as Map<String, dynamic>;
          _palabraRecibida = primerJugador['palabra'] as String?;
          _soyImpostor = primerJugador['esImpostor'] as bool? ?? false;
        } else {
          _palabraRecibida = mensaje.datos['palabra'] as String?;
          _soyImpostor = mensaje.datos['esImpostor'] as bool? ?? false;
        }
        _datosPartida = mensaje.datos;
        notifyListeners();
        break;
      case TipoMensaje.fase:
        _faseActual = mensaje.datos['fase'] as String?;
        _datosPartida = mensaje.datos;
        notifyListeners();
        break;
      case TipoMensaje.votacionResultado:
      case TipoMensaje.partidaFin:
      case TipoMensaje.errorOperacion:
        _datosPartida = mensaje.datos;
        notifyListeners();
        break;
      case TipoMensaje.jugadorDesconectado:
        notifyListeners();
        break;
      default:
        break;
    }
  }

  // ==================== ENVIO ====================

  Future<void> enviarMensaje(String endpointId, MensajeP2P mensaje) async {
    try {
      await Nearby().sendBytesPayload(endpointId, mensaje.toBytes());
    } catch (e) {
      debugPrint('Error enviando a $endpointId: $e');
    }
  }

  Future<void> enviarATodos(MensajeP2P mensaje) async {
    for (final id in _jugadores.keys) {
      await enviarMensaje(id, mensaje);
    }
  }

  Future<void> crearUsuarioSala(
    String nombre, {
    bool seleccionar = true,
    String? nick,
    String? avatar,
    int fuego = 0,
    List<String> medallas = const [],
  }) async {
    final nombreLimpio = nombre.trim();
    if (nombreLimpio.isEmpty) return;
    final clientId = _miClientId;
    final usuario = Usuario(
      id: 'u-${DateTime.now().microsecondsSinceEpoch}',
      nombre: nombreLimpio,
      nick: nick,
      avatar: avatar,
      foto: avatar,
      creadoPorClienteId: clientId,
      fuego: fuego,
      medallas: medallas,
    );
    if (_esHost && _estadoSala != null && clientId != null) {
      final resultado = _estadoSala!.crearUsuario(usuario);
      if (resultado.exitoso && seleccionar) {
        _estadoSala!.seleccionarUsuario(
          usuarioId: usuario.id,
          clienteId: clientId,
        );
      }
      await _broadcastEstadoSala();
      return;
    }
    final hostId = _hostEndpointId;
    if (hostId == null) return;
    await enviarMensaje(
      hostId,
      MensajeP2P(
        tipo: TipoMensaje.crearUsuario,
        datos: {
          'clientId': clientId,
          'seleccionar': seleccionar,
          'usuario': usuario.toJson(),
        },
      ),
    );
  }

  Future<void> seleccionarUsuarioSala(String usuarioId) async {
    final clientId = _miClientId;
    if (_esHost && _estadoSala != null && clientId != null) {
      _estadoSala!.seleccionarUsuario(usuarioId: usuarioId, clienteId: clientId);
      await _broadcastEstadoSala();
      return;
    }
    final hostId = _hostEndpointId;
    if (hostId == null) return;
    await enviarMensaje(
      hostId,
      MensajeP2P(
        tipo: TipoMensaje.seleccionarUsuario,
        datos: {'clientId': clientId, 'usuarioId': usuarioId},
      ),
    );
  }

  Future<void> liberarUsuarioSala(String usuarioId) async {
    final clientId = _miClientId;
    if (_esHost && _estadoSala != null && clientId != null) {
      _estadoSala!.liberarUsuario(
        usuarioId: usuarioId,
        solicitanteClientId: clientId,
      );
      await _broadcastEstadoSala();
      return;
    }
    final hostId = _hostEndpointId;
    if (hostId == null) return;
    await enviarMensaje(
      hostId,
      MensajeP2P(
        tipo: TipoMensaje.liberarUsuario,
        datos: {'clientId': clientId, 'usuarioId': usuarioId},
      ),
    );
  }

  Future<void> eliminarUsuarioSala(String usuarioId) async {
    final clientId = _miClientId;
    if (_esHost && _estadoSala != null && clientId != null) {
      _estadoSala!.eliminarUsuario(
        usuarioId: usuarioId,
        solicitanteClientId: clientId,
      );
      await _broadcastEstadoSala();
      return;
    }
    final hostId = _hostEndpointId;
    if (hostId == null) return;
    await enviarMensaje(
      hostId,
      MensajeP2P(
        tipo: TipoMensaje.eliminarUsuario,
        datos: {'clientId': clientId, 'usuarioId': usuarioId},
      ),
    );
  }

  Future<void> asumirUsuariosDesconectados() async {
    final sala = _estadoSala;
    if (!_esHost || sala == null) return;
    var huboCambios = false;
    for (final cliente in sala.clientesDesconectados) {
      final reasignados = sala.reasignarUsuariosDeCliente(
        clientIdOrigen: cliente.clientId,
        clientIdDestino: sala.hostClientId,
      );
      huboCambios = huboCambios || reasignados > 0;
    }
    if (huboCambios) {
      await _broadcastEstadoSala();
    }
  }

  // ==================== HOST: ACCIONES DE JUEGO ====================

  Future<void> enviarInicioPartida({
    required String palabraSecreta,
    required String categoria,
    required Map<String, bool> impostores,
  }) async {
    for (final entry in _jugadores.entries) {
      final esImpostor = impostores[entry.key] ?? false;
      await enviarMensaje(
        entry.key,
        MensajeP2P(
          tipo: TipoMensaje.partidaInicio,
          datos: {
            'palabra': esImpostor ? null : palabraSecreta,
            'esImpostor': esImpostor,
            'categoria': categoria,
            'numJugadores': _jugadores.length + 1,
            'roomId': _roomId,
          },
        ),
      );
    }
  }

  Future<void> enviarInicioPartidaMulti({
    required List<AsignacionJugador> asignaciones,
    required String palabraSecreta,
    required String categoria,
    required Map<String, bool> impostoresPorJugadorId,
    required List<Map<String, dynamic>> jugadoresTodos,
  }) async {
    final payloads = InicioPartidaMultijugador.crearPayloadsPorCliente(
      asignaciones: asignaciones,
      palabraSecreta: palabraSecreta,
      categoria: categoria,
      impostoresPorJugadorId: impostoresPorJugadorId,
    );

    for (final payload in payloads.values) {
      final endpointId = payload.endpointId;
      if (endpointId == null) continue;
      final datos = payload.toJson();
      datos['jugadoresTodos'] = jugadoresTodos;
      datos['roomId'] = _roomId;
      await enviarMensaje(
        endpointId,
        MensajeP2P(tipo: TipoMensaje.partidaInicio, datos: datos),
      );
    }
  }

  Future<void> enviarCambioFase(
    String fase, [
    Map<String, dynamic>? extra,
  ]) async {
    final datos = {'fase': fase, ...?extra};
    await enviarATodos(MensajeP2P(tipo: TipoMensaje.fase, datos: datos));
  }

  Future<void> enviarResultadoVotacion(Map<String, dynamic> resultado) async {
    await enviarATodos(
      MensajeP2P(tipo: TipoMensaje.votacionResultado, datos: resultado),
    );
  }

  Future<void> enviarFinPartida(Map<String, dynamic> resultado) async {
    await enviarATodos(
      MensajeP2P(tipo: TipoMensaje.partidaFin, datos: resultado),
    );
  }

  // ==================== LIMPIEZA ====================

  Future<void> desconectar() async {
    _heartbeatTimer?.cancel();
    try {
      await Nearby().stopAllEndpoints();
      if (_anunciando) await Nearby().stopAdvertising();
      if (_buscando) await Nearby().stopDiscovery();
    } catch (e) {
      debugPrint('Error desconectando: $e');
    }

    _esHost = false;
    _conectado = false;
    _buscando = false;
    _anunciando = false;
    _miEndpointId = null;
    _hostEndpointId = null;
    _roomId = null;
    _miClientId = null;
    _nombreSala = null;
    _miNombre = null;
    _miNick = null;
    _miAvatar = null;
    _miFuego = 0;
    _miMedallas = const [];
    _palabraRecibida = null;
    _soyImpostor = null;
    _faseActual = null;
    _datosPartida = null;
    _estadoSala = null;
    _jugadores.clear();
    _hostsEncontrados.clear();
    _usuariosPool.clear();
    _heartbeatTimer = null;
    notifyListeners();
  }

  String generarDatosQR(String nombreSala) {
    return json.encode({
      'app': 'farolero',
      'sala': nombreSala,
      'host': _miNombre,
      'roomId': _roomId,
    });
  }

  static Map<String, dynamic>? parsearQR(String datos) {
    try {
      final mapa = json.decode(datos) as Map<String, dynamic>;
      if (mapa['app'] == 'farolero') return mapa;
      return null;
    } catch (_) {
      return null;
    }
  }

  @override
  void dispose() {
    _heartbeatTimer?.cancel();
    desconectar();
    super.dispose();
  }
}
