namespace GeoSus.Server;

using System.Text.Json;
using System.Text.Json.Serialization;

#region Základní typy

public record struct Position(double Lat, double Lon)
{
    // Haversine vzdálenost v metrech
    public double DistanceTo(Position other)
    {
        const double R = 6371000; // Poloměr Země v metrech
        var lat1 = Lat * Math.PI / 180;
        var lat2 = other.Lat * Math.PI / 180;
        var dLat = (other.Lat - Lat) * Math.PI / 180;
        var dLon = (other.Lon - Lon) * Math.PI / 180;
        
        var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
                Math.Cos(lat1) * Math.Cos(lat2) *
                Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
        var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
        
        return R * c;
    }
}

public enum PlayerRole { Crew, Impostor }
public enum PlayerState { Alive, Dead }
public enum CheatStatus { Ok, Warn, Restrict, Kicked }
public enum GamePhase { Lobby, Loading, Playing, Meeting, Voting, Ended }
public enum TaskType { Instant }
public enum MeetingType { BodyReport, Emergency }

// Sabotage system
public enum SabotageType
{
    /// <summary>Comms Blackout - Cannot report bodies or call emergency meetings</summary>
    CommsBlackout,
    /// <summary>Critical Meltdown - Must be repaired in time or impostors win (requires 2 simultaneous repairs)</summary>
    CriticalMeltdown
}

public enum SabotageState
{
    Inactive,
    Active,
    Repaired
}

// Map data types
public enum PathType
{
    Footway,
    Steps,
    Cycleway,
    Residential,
    Service,
    Road,
    Track,
    Other
}

public enum MapAreaType
{
    Park,
    Playground,
    Garden,
    Water,
    Other
}

public enum MapPOIType
{
    FoodDrink,
    Finance,
    Health,
    Education,
    Transport,
    Shop,
    Amenity,
    Other
}

#endregion

#region Herní entity

public class Player
{
    public required string ClientUuid { get; set; }
    public required string DisplayName { get; set; }
    public Position Position { get; set; }
    public PlayerRole Role { get; set; } = PlayerRole.Crew;
    public PlayerState State { get; set; } = PlayerState.Alive;
    public CheatStatus CheatStatus { get; set; } = CheatStatus.Ok;
    public int CheatScore { get; set; }
    public DateTime ConnectedAt { get; set; } = DateTime.UtcNow;
    public DateTime LastPositionUpdate { get; set; } = DateTime.UtcNow;
    public DateTime? LastKillTime { get; set; }
    public DateTime? LastEmergencyMeetingTime { get; set; }
    public int EmergencyMeetingsUsed { get; set; }
    public List<string> CompletedTaskIds { get; set; } = new();
    public List<GameTask> Tasks { get; set; } = new(); // Per-player tasks
    public string? CurrentTaskId { get; set; }
    public DateTime? TaskStartTime { get; set; }
    public bool IsOwner { get; set; }
    public bool IsReady { get; set; }
    public long LastEventId { get; set; }
    public int Ping { get; set; }
    
    // Historie pozic pro anti-cheat
    public Queue<(Position Pos, DateTime Time)> PositionHistory { get; set; } = new();
}

public class Body
{
    public required string BodyId { get; set; }
    public required string VictimId { get; set; }
    public required string KillerId { get; set; }
    public Position Location { get; set; }
    public DateTime KilledAt { get; set; } = DateTime.UtcNow;
    public bool Reported { get; set; }
    public string? ReportedBy { get; set; }
}

public class GameTask
{
    public required string TaskId { get; set; }
    public required string Name { get; set; }
    public Position Location { get; set; }
    public TaskType Type { get; set; } = TaskType.Instant;
}

public class Meeting
{
    public required string MeetingId { get; set; }
    public MeetingType Type { get; set; }
    public string? ReportedBodyId { get; set; }
    public required string CallerId { get; set; }
    public Position MeetingLocation { get; set; }
    public DateTime StartTime { get; set; } = DateTime.UtcNow;
    public DateTime ArrivalDeadline { get; set; }
    public DateTime? DiscussionEndTime { get; set; }
    public DateTime VotingEndTime { get; set; }
    public HashSet<string> ArrivedPlayers { get; set; } = new();
    public Dictionary<string, string?> Votes { get; set; } = new(); // Voter -> Target (null = skip)
    public DateTime? LastVoteChangeTime { get; set; }
}

/// <summary>
/// Active sabotage instance
/// </summary>
public class Sabotage
{
    public required string SabotageId { get; set; }
    public SabotageType Type { get; set; }
    public SabotageState State { get; set; } = SabotageState.Active;
    public required string InitiatorId { get; set; }
    public DateTime StartTime { get; set; } = DateTime.UtcNow;
    public DateTime? Deadline { get; set; } // For critical meltdown
    public DateTime? RepairedAt { get; set; }
    public string? RepairedBy { get; set; }
    
    /// <summary>
    /// Repair stations for this sabotage.
    /// CommsBlackout: 1 station
    /// CriticalMeltdown: 2 stations that need simultaneous activation
    /// </summary>
    public List<RepairStation> RepairStations { get; set; } = new();
    
    /// <summary>
    /// How many stations need to be simultaneously active to repair.
    /// CommsBlackout: 1, CriticalMeltdown: 2
    /// </summary>
    public int RequiredSimultaneousRepairs { get; set; } = 1;
}

/// <summary>
/// Repair station for fixing sabotages
/// </summary>
public class RepairStation
{
    public required string StationId { get; set; }
    public required string Name { get; set; }
    public Position Location { get; set; }
    public bool IsBeingRepaired { get; set; }
    public string? RepairingPlayerId { get; set; }
    public DateTime? RepairStartTime { get; set; }
    
    /// <summary>
    /// Has this station been successfully repaired
    /// </summary>
    public bool IsRepaired { get; set; }
    
    /// <summary>
    /// How long player must hold to complete repair at this station
    /// </summary>
    public int RepairDurationMs { get; set; } = 3000;
}

#endregion

#region Map Data

/// <summary>
/// Complete map data for a play area - sent to clients for rendering
/// </summary>
public class MapData
{
    public Position Center { get; set; }
    public double RadiusMeters { get; set; }
    public DateTime FetchedAt { get; set; }
    
    /// <summary>Buildings to render (polygons)</summary>
    public List<MapBuilding> Buildings { get; set; } = new();
    
    /// <summary>Walkable pathways (lines)</summary>
    public List<MapPathway> Pathways { get; set; } = new();
    
    /// <summary>Parks, green areas (polygons)</summary>
    public List<MapArea> Areas { get; set; } = new();
    
    /// <summary>Points of interest (single points)</summary>
    public List<MapPOI> PointsOfInterest { get; set; } = new();
    
    /// <summary>All positions reachable from center via walkable paths (within play area)</summary>
    [JsonIgnore] // Too large for network, computed server-side only
    public List<Position> ReachablePositions { get; set; } = new();
}

/// <summary>Building outline for rendering</summary>
public class MapBuilding
{
    public required string Id { get; set; }
    public List<Position> Outline { get; set; } = new();
    public string BuildingType { get; set; } = "yes";
    public string? Name { get; set; }
    public int Levels { get; set; } = 1;
}

/// <summary>Walkable pathway for rendering and reachability</summary>
public class MapPathway
{
    public required string Id { get; set; }
    public List<Position> Points { get; set; } = new();
    public PathType PathType { get; set; }
    public string? Name { get; set; }
    public bool IsWalkable { get; set; } = true;
    public double Width { get; set; } = 2.0;
    
    [JsonIgnore]
    public bool IsFullyReachable { get; set; }
    [JsonIgnore]
    public bool IsPartiallyReachable { get; set; }
}

/// <summary>Area like park or garden</summary>
public class MapArea
{
    public required string Id { get; set; }
    public List<Position> Outline { get; set; } = new();
    public MapAreaType AreaType { get; set; }
    public string? Name { get; set; }
}

/// <summary>Point of interest for task placement hints</summary>
public class MapPOI
{
    public required string Id { get; set; }
    public Position Position { get; set; }
    public MapPOIType PoiType { get; set; }
    public string? Name { get; set; }
}

/// <summary>Simplified map data sent to clients (smaller payload)</summary>
public class MapDataPayload
{
    public Position Center { get; set; }
    public double RadiusMeters { get; set; }
    
    /// <summary>Buildings: [[lat,lon,lat,lon,...], ...]</summary>
    public List<double[]> Buildings { get; set; } = new();
    
    /// <summary>Building types: ["residential", "commercial", ...]</summary>
    public List<string> BuildingTypes { get; set; } = new();
    
    /// <summary>Pathways: [[lat,lon,lat,lon,...], ...]</summary>
    public List<double[]> Pathways { get; set; } = new();
    
    /// <summary>Pathway types: [0=footway, 1=steps, ...]</summary>
    public List<int> PathwayTypes { get; set; } = new();
    
    /// <summary>Areas (parks): [[lat,lon,lat,lon,...], ...]</summary>
    public List<double[]> Areas { get; set; } = new();
    
    /// <summary>Area types</summary>
    public List<int> AreaTypes { get; set; } = new();
    
    /// <summary>POIs: [lat, lon, type, lat, lon, type, ...]</summary>
    public List<double> POIs { get; set; } = new();
    
    /// <summary>Convert from full MapData to compact payload</summary>
    public static MapDataPayload FromMapData(MapData data)
    {
        var payload = new MapDataPayload
        {
            Center = data.Center,
            RadiusMeters = data.RadiusMeters
        };
        
        foreach (var b in data.Buildings)
        {
            var coords = new double[b.Outline.Count * 2];
            for (int i = 0; i < b.Outline.Count; i++)
            {
                coords[i * 2] = b.Outline[i].Lat;
                coords[i * 2 + 1] = b.Outline[i].Lon;
            }
            payload.Buildings.Add(coords);
            payload.BuildingTypes.Add(b.BuildingType);
        }
        
        foreach (var p in data.Pathways)
        {
            var coords = new double[p.Points.Count * 2];
            for (int i = 0; i < p.Points.Count; i++)
            {
                coords[i * 2] = p.Points[i].Lat;
                coords[i * 2 + 1] = p.Points[i].Lon;
            }
            payload.Pathways.Add(coords);
            payload.PathwayTypes.Add((int)p.PathType);
        }
        
        foreach (var a in data.Areas)
        {
            var coords = new double[a.Outline.Count * 2];
            for (int i = 0; i < a.Outline.Count; i++)
            {
                coords[i * 2] = a.Outline[i].Lat;
                coords[i * 2 + 1] = a.Outline[i].Lon;
            }
            payload.Areas.Add(coords);
            payload.AreaTypes.Add((int)a.AreaType);
        }
        
        foreach (var poi in data.PointsOfInterest)
        {
            payload.POIs.Add(poi.Position.Lat);
            payload.POIs.Add(poi.Position.Lon);
            payload.POIs.Add((int)poi.PoiType);
        }
        
        return payload;
    }
}

#endregion

#region Protokol - Zprávy

public abstract class Message
{
    [JsonPropertyName("type")]
    public abstract string Type { get; }
    
    [JsonPropertyName("clientSeq")]
    public int ClientSeq { get; set; }
    
    [JsonPropertyName("actionId")]
    public string? ActionId { get; set; }
}

// Handshake
public class ClientHello : Message
{
    public override string Type => "ClientHello";
    public string ProtocolVersion { get; set; } = "1.0";
    public required string ClientUuid { get; set; }
    public string? DisplayName { get; set; }
}

public class ServerHello : Message
{
    public override string Type => "ServerHello";
    public required string RsaPublicKeyPem { get; set; }
    public required string ServerId { get; set; }
}

public class KeyExchange : Message
{
    public override string Type => "KeyExchange";
    public required string EncryptedSessionKey { get; set; }
    public required string EncryptedIV { get; set; }
}

public class KeyExchangeAck : Message
{
    public override string Type => "KeyExchangeAck";
    public string Status { get; set; } = "success";
}

// Lobby
public class CreateLobby : Message
{
    public override string Type => "CreateLobby";
    public string? Password { get; set; }
    public Position? PlayAreaCenter { get; set; }
    public double PlayAreaRadius { get; set; } = 500; // metry
    public int ImpostorCount { get; set; } = 1;
    public int TaskCount { get; set; } = 5;
}

public class CreateLobbyResponse : Message
{
    public override string Type => "CreateLobbyResponse";
    public bool Success { get; set; }
    public string? JoinCode { get; set; }
    public string? LobbyId { get; set; }
    public string? Error { get; set; }
    public LobbyState? LobbyState { get; set; }
}

public class JoinLobby : Message
{
    public override string Type => "JoinLobby";
    public required string JoinCode { get; set; }
    public string? Password { get; set; }
}

public class JoinLobbyResponse : Message
{
    public override string Type => "JoinLobbyResponse";
    public bool Success { get; set; }
    public string? LobbyId { get; set; }
    public string? Error { get; set; }
    public LobbyState? LobbyState { get; set; }
}

public class LeaveLobby : Message
{
    public override string Type => "LeaveLobby";
}

public class KickPlayer : Message
{
    public override string Type => "KickPlayer";
    public required string TargetClientUuid { get; set; }
}

public class StartGame : Message
{
    public override string Type => "StartGame";
}

public class ReturnToLobby : Message
{
    public override string Type => "ReturnToLobby";
}

/// <summary>
/// Client confirms it received map data and is ready to play
/// </summary>
public class MapDataReceived : Message
{
    public override string Type => "MapDataReceived";
}

// Hra
public class UpdatePosition : Message
{
    public override string Type => "UpdatePosition";
    public Position Position { get; set; }
}

public class PositionBroadcast : Message
{
    public override string Type => "PositionBroadcast";
    public List<PlayerPositionInfo> Players { get; set; } = new();
}

public class PlayerPositionInfo
{
    public required string ClientUuid { get; set; }
    public Position Position { get; set; }
    public PlayerState State { get; set; }
}

public class KillAttempt : Message
{
    public override string Type => "KillAttempt";
    public required string TargetClientUuid { get; set; }
}

public class ReportBody : Message
{
    public override string Type => "ReportBody";
    public required string BodyId { get; set; }
}

public class CallEmergencyMeeting : Message
{
    public override string Type => "CallEmergencyMeeting";
}

public class CastVote : Message
{
    public override string Type => "CastVote";
    public string? TargetClientUuid { get; set; } // null = skip
}

public class TaskStart : Message
{
    public override string Type => "TaskStart";
    public required string TaskId { get; set; }
}

public class TaskProgress : Message
{
    public override string Type => "TaskProgress";
    public required string TaskId { get; set; }
    public int Step { get; set; } = 1;
}

public class TaskComplete : Message
{
    public override string Type => "TaskComplete";
    public required string TaskId { get; set; }
}

// Ping/Heartbeat
public class Ping : Message
{
    public override string Type => "Ping";
    public long ClientTime { get; set; }
}

public class Pong : Message
{
    public override string Type => "Pong";
    public long ClientTime { get; set; }
    public long ServerTime { get; set; }
}

// Reconnect
public class Reconnect : Message
{
    public override string Type => "Reconnect";
    public required string LobbyId { get; set; }
    public long LastEventId { get; set; }
}

public class ReconnectResponse : Message
{
    public override string Type => "ReconnectResponse";
    public bool Success { get; set; }
    public string? Error { get; set; }
    public LobbySnapshot? Snapshot { get; set; }
    public List<GameEvent>? MissedEvents { get; set; }
}

// Ack
public class Ack : Message
{
    public override string Type => "Ack";
    public int AckedSeq { get; set; }
    public bool Success { get; set; }
    public string? Error { get; set; }
}

// Error
public class ErrorMessage : Message
{
    public override string Type => "Error";
    public required string ErrorCode { get; set; }
    public required string ErrorText { get; set; }
}

// Sabotage messages
public class StartSabotage : Message
{
    public override string Type => "StartSabotage";
    public SabotageType SabotageType { get; set; }
    public Position? TargetLocation { get; set; } // For zone lockdown
}

public class ActivateRepairStation : Message
{
    public override string Type => "ActivateRepairStation";
    public required string StationId { get; set; }
}

public class DeactivateRepairStation : Message
{
    public override string Type => "DeactivateRepairStation";
    public required string StationId { get; set; }
}

#endregion

#region Eventy

public class GameEvent : Message
{
    public override string Type => "GameEvent";
    public long EventId { get; set; }
    public long ServerSeq { get; set; }
    public DateTime Timestamp { get; set; } = DateTime.UtcNow;
    public string? Actor { get; set; }
    public required string EventType { get; set; }
    public object? Payload { get; set; }
}

// Payload typy pro eventy
public class LobbyCreatedPayload
{
    public required string LobbyId { get; set; }
    public required string JoinCode { get; set; }
    public required string OwnerId { get; set; }
}

public class PlayerJoinedPayload
{
    public required string ClientUuid { get; set; }
    public required string DisplayName { get; set; }
}

public class PlayerLeftPayload
{
    public required string ClientUuid { get; set; }
    public string? Reason { get; set; }
}

/// <summary>
/// Sent when owner clicks StartGame - clients should show loading screen
/// </summary>
public class GameStartingPayload
{
    public string? InitiatorId { get; set; }
    public string Message { get; set; } = "Loading map data...";
}

/// <summary>
/// Sent when map data is ready and distributed to all clients
/// </summary>
public class MapDataReadyPayload
{
    public MapDataPayload? MapData { get; set; }
    public Position PlayAreaCenter { get; set; }
    public double PlayAreaRadius { get; set; }
}

/// <summary>
/// Sent when a client confirms they received map data
/// </summary>
public class PlayerMapDataReceivedPayload
{
    public required string ClientUuid { get; set; }
    public string DisplayName { get; set; } = "";
    public int PlayersReady { get; set; }
    public int TotalPlayers { get; set; }
}

/// <summary>
/// Sent when all clients have confirmed map data and game actually starts
/// </summary>
public class GameStartedPayload
{
    public int ImpostorCount { get; set; }
    public int TaskCount { get; set; }
}

public class RoleAssignedPayload
{
    public required string ClientUuid { get; set; }
    public PlayerRole Role { get; set; }
    public List<GameTask>? Tasks { get; set; }
}

public class PlayerKilledPayload
{
    public required string VictimId { get; set; }
    public required string KillerId { get; set; }
    public required string BodyId { get; set; }
    public Position Location { get; set; }
}

public class BodyReportedPayload
{
    public required string ReporterId { get; set; }
    public required string BodyId { get; set; }
    public required string VictimId { get; set; }
}

public class EmergencyMeetingCalledPayload
{
    public required string CallerId { get; set; }
}

public class MeetingStartedPayload
{
    public required string MeetingId { get; set; }
    public MeetingType Type { get; set; }
    public Position MeetingLocation { get; set; }
    public DateTime ArrivalDeadline { get; set; }
    public DateTime? DiscussionEndTime { get; set; }
    public DateTime VotingEndTime { get; set; }
}

public class PlayerArrivedAtMeetingPayload
{
    public required string ClientUuid { get; set; }
    public required string MeetingId { get; set; }
}

public class PlayerVotedPayload
{
    public required string VoterId { get; set; }
    public string? TargetId { get; set; } // null = skip
}

public class VotingClosedPayload
{
    public Dictionary<string, int> VoteCounts { get; set; } = new();
    public string? EjectedPlayerId { get; set; }
    public bool WasTie { get; set; }
}

public class PlayerEjectedPayload
{
    public required string ClientUuid { get; set; }
    public PlayerRole Role { get; set; }
}

public class TaskCompletedPayload
{
    public required string ClientUuid { get; set; }
    public required string TaskId { get; set; }
    public int TotalCompleted { get; set; }
    public int TotalTasks { get; set; }
}

public class GameEndedPayload
{
    public required string WinningFaction { get; set; } // "Crew" nebo "Impostor"
    public required string Reason { get; set; }
    public List<string> Winners { get; set; } = new();
}

public class ReturnedToLobbyPayload
{
    public string Message { get; set; } = "";
}

public class HostChangedPayload
{
    public required string NewHostId { get; set; }
    public required string PreviousHostId { get; set; }
}

public class CheatDetectedPayload
{
    public required string ClientUuid { get; set; }
    public required string Violation { get; set; }
    public int NewCheatScore { get; set; }
    public CheatStatus NewStatus { get; set; }
}

// Sabotage event payloads
public class SabotageStartedPayload
{
    public required string SabotageId { get; set; }
    public SabotageType Type { get; set; }
    public required string InitiatorId { get; set; }
    public DateTime? Deadline { get; set; } // For critical meltdown
    public List<RepairStationInfo> RepairStations { get; set; } = new();
    public int RequiredSimultaneousRepairs { get; set; }
}

public class RepairStationInfo
{
    public required string StationId { get; set; }
    public required string Name { get; set; }
    public Position Location { get; set; }
    public int RepairDurationMs { get; set; }
}

public class RepairStartedPayload
{
    public required string SabotageId { get; set; }
    public required string StationId { get; set; }
    public required string PlayerId { get; set; }
}

public class RepairStoppedPayload
{
    public required string SabotageId { get; set; }
    public required string StationId { get; set; }
    public required string PlayerId { get; set; }
}

public class RepairProgressPayload
{
    public required string SabotageId { get; set; }
    public required string StationId { get; set; }
    public required string PlayerId { get; set; }
    public int ProgressMs { get; set; }
    public int RequiredMs { get; set; }
}

public class SabotageRepairedPayload
{
    public required string SabotageId { get; set; }
    public SabotageType Type { get; set; }
    public List<string> RepairerIds { get; set; } = new(); // Players who helped repair
}

public class SabotageMeltdownPayload
{
    public required string SabotageId { get; set; }
    // Impostor wins - game ends
}

#endregion

#region State

public class LobbyState
{
    public required string LobbyId { get; set; }
    public required string JoinCode { get; set; }
    public string? OwnerId { get; set; }
    public GamePhase Phase { get; set; } = GamePhase.Lobby;
    public List<PlayerInfo> Players { get; set; } = new();
    public Position PlayAreaCenter { get; set; }
    public double PlayAreaRadius { get; set; }
    public int ImpostorCount { get; set; }
    public bool HasPassword { get; set; }
    public DateTime CreatedAt { get; set; }
    
    /// <summary>Map data for client rendering (null if Overpass disabled or failed)</summary>
    public MapDataPayload? MapData { get; set; }
    
    /// <summary>True if map data has been loaded (or Overpass is disabled)</summary>
    public bool MapDataReady { get; set; } = true;
}

public class PlayerInfo
{
    public required string ClientUuid { get; set; }
    public required string DisplayName { get; set; }
    public bool IsOwner { get; set; }
    public bool IsReady { get; set; }
    public PlayerState State { get; set; }
    // Role je viditelná pouze pro daného hráče, ne v broadcast
}

public class LobbySnapshot
{
    public required string LobbyId { get; set; }
    public long LastEventId { get; set; }
    public DateTime Timestamp { get; set; }
    public required string Checksum { get; set; }
    public GamePhase Phase { get; set; }
    public List<Player> Players { get; set; } = new();
    public List<Body> Bodies { get; set; } = new();
    public List<GameTask> Tasks { get; set; } = new();
    public Meeting? CurrentMeeting { get; set; }
    public Position PlayAreaCenter { get; set; }
    public double PlayAreaRadius { get; set; }
    public int ImpostorCount { get; set; }
    public TiePolicy TiePolicy { get; set; }
}

#endregion

#region Serializace

public static class MessageSerializer
{
    private static readonly Dictionary<string, Type> MessageTypes = new()
    {
        ["ClientHello"] = typeof(ClientHello),
        ["ServerHello"] = typeof(ServerHello),
        ["KeyExchange"] = typeof(KeyExchange),
        ["KeyExchangeAck"] = typeof(KeyExchangeAck),
        ["CreateLobby"] = typeof(CreateLobby),
        ["CreateLobbyResponse"] = typeof(CreateLobbyResponse),
        ["JoinLobby"] = typeof(JoinLobby),
        ["JoinLobbyResponse"] = typeof(JoinLobbyResponse),
        ["LeaveLobby"] = typeof(LeaveLobby),
        ["KickPlayer"] = typeof(KickPlayer),
        ["StartGame"] = typeof(StartGame),
        ["ReturnToLobby"] = typeof(ReturnToLobby),
        ["MapDataReceived"] = typeof(MapDataReceived),
        ["UpdatePosition"] = typeof(UpdatePosition),
        ["PositionBroadcast"] = typeof(PositionBroadcast),
        ["KillAttempt"] = typeof(KillAttempt),
        ["ReportBody"] = typeof(ReportBody),
        ["CallEmergencyMeeting"] = typeof(CallEmergencyMeeting),
        ["CastVote"] = typeof(CastVote),
        ["TaskStart"] = typeof(TaskStart),
        ["TaskProgress"] = typeof(TaskProgress),
        ["TaskComplete"] = typeof(TaskComplete),
        ["Ping"] = typeof(Ping),
        ["Pong"] = typeof(Pong),
        ["Reconnect"] = typeof(Reconnect),
        ["ReconnectResponse"] = typeof(ReconnectResponse),
        ["Ack"] = typeof(Ack),
        ["Error"] = typeof(ErrorMessage),
        ["GameEvent"] = typeof(GameEvent),
        ["StartSabotage"] = typeof(StartSabotage),
        ["ActivateRepairStation"] = typeof(ActivateRepairStation),
        ["DeactivateRepairStation"] = typeof(DeactivateRepairStation)
    };
    
    public static byte[] Serialize(Message msg)
    {
        return JsonSerializer.SerializeToUtf8Bytes(msg, msg.GetType(), JsonOptions.Default);
    }
    
    public static byte[] Serialize(GameEvent evt)
    {
        return JsonSerializer.SerializeToUtf8Bytes(evt, JsonOptions.Default);
    }
    
    public static Message? Deserialize(ReadOnlySpan<byte> data)
    {
        // Nejdřív zjistíme typ
        using var doc = JsonDocument.Parse(data.ToArray());
        if (!doc.RootElement.TryGetProperty("type", out var typeProp))
            return null;
            
        var typeName = typeProp.GetString();
        if (typeName == null || !MessageTypes.TryGetValue(typeName, out var type))
            return null;
            
        return (Message?)JsonSerializer.Deserialize(data, type, JsonOptions.Default);
    }
    
    public static GameEvent? DeserializeEvent(ReadOnlySpan<byte> data)
    {
        return JsonSerializer.Deserialize<GameEvent>(data, JsonOptions.Default);
    }
}

#endregion
