using BepInEx.Unity.IL2CPP.Utils;
using HarmonyLib;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using static BanMod.Utils;

namespace BanMod
{
    public enum ProfilerHintStrength
    {
        Mild = 0,
        Medium = 1,
        Strong = 2,
        TaskBased = 3
    }

    public static class Profiler
    {
        public static byte ProfilerId = byte.MaxValue;
        public static bool ProfilerSelected = false;

        private static byte LastImpostorId = byte.MaxValue;
        private static string LastHintKey = string.Empty;

        private static readonly Dictionary<byte, HashSet<string>> UsedHintKeysByImpostor =
            new Dictionary<byte, HashSet<string>>();

        private static readonly HashSet<string> ExhaustedCategoriesNotified =
            new HashSet<string>();

        private static int MeetingSequence = 0;
        private static int LastSentMeetingSequence = -1;
        private static int LastCapturedMeetingHudId = -1;
        private static bool HintCoroutineRunning = false;

        private static readonly Dictionary<byte, ImpostorSnapshot> MeetingSnapshots =
            new Dictionary<byte, ImpostorSnapshot>();

        private sealed class ImpostorSnapshot
        {
            public byte PlayerId;
            public string PlayerName = string.Empty;
            public int Level;
            public bool HasPet;
            public bool HasHat;
            public int ColorId = -1;
            public string RoomName = string.Empty;
            public Vector2 Position;
        }

        private sealed class HintCandidate
        {
            public string Key = string.Empty;
            public string Text = string.Empty;
        }
        public static void SendProfilerMessage()
        {
            if (ProfilerId == byte.MaxValue)
                return;

            var allPlayers = BanMod.AllPlayerControls;
            var ProfilerPlayer =
                allPlayers.FirstOrDefault(p => p.PlayerId == ProfilerId);

            if (ProfilerPlayer == null ||
                ProfilerPlayer.Data == null ||
                ProfilerPlayer.Data.IsDead)
            {
                return;
            }

            string msg = string.Format(Translator.GetString("ProfilerInfo"));

            if (AmongUsClient.Instance.AmHost &&
                PlayerControl.LocalPlayer.Data.IsDead)
            {
                Utils.RequestProxyMessage(msg, ProfilerId);
                MessageBlocker.UpdateLastMessageTime();
            }
            else
            {
                Utils.SendMessage(msg, ProfilerId);
                MessageBlocker.UpdateLastMessageTime();
            }
        }
        public static ProfilerHintStrength ConfiguredStrength
        {
            get
            {
                int value = Mathf.Clamp(Options.ProfilerHintMode.GetInt(), 0, 3);
                return (ProfilerHintStrength)value;
            }
        }

        public static void OnStart()
        {
            ResetProfiler();

            if (!Options.Profiler.GetBool())
                return;

            // Solo l'host sceglie il ruolo e invia gli indizi privati.
            if (!AmongUsClient.Instance.AmHost)
                return;

            SelectProfiler();

            if (!ProfilerSelected)
                BMLogger.Info("[Profiler] Profiler non assegnato.");
        }

        public static void SelectProfiler()
        {
            if (!AmongUsClient.Instance.AmHost ||
                !Options.Profiler.GetBool())
            {
                return;
            }

            var candidates = BanMod.AllPlayerControls
                .Where(p => p != null
                            && p.Data != null
                            && !p.Data.IsDead
                            && !p.Data.Disconnected
                            && p.PlayerId != Judge.JudgeId
                            && p.PlayerId != Guesser.SpecialKillerId
                            && p.PlayerId != Jester.JesterId
                            && p.PlayerId != Watcher.WatcherId
                            && p.PlayerId != Exiler.ExilerId
                            && !Scientist(p)
                            && !Engineer(p)
                            && !Tracker(p)
                            && !Detective(p)
                            && !IsImpostorAligned(p))
                .ToList();

            if (candidates.Count == 0)
            {
                ProfilerId = byte.MaxValue;
                ProfilerSelected = false;

                BMLogger.Info(
                    "[Profiler] Nessun candidato valido per Profiler trovato.");

                return;
            }

            PlayerControl selected =
                candidates[UnityEngine.Random.Range(0, candidates.Count)];

            ProfilerId = selected.PlayerId;
            ProfilerSelected = true;

            BMLogger.Info(
                $"[Profiler] Profiler assegnato al PlayerId {ProfilerId}.");
        }

        public static void ResetProfiler()
        {
            ProfilerId = byte.MaxValue;
            ProfilerSelected = false;

            LastImpostorId = byte.MaxValue;
            LastHintKey = string.Empty;

            MeetingSequence = 0;
            LastSentMeetingSequence = -1;
            LastCapturedMeetingHudId = -1;
            HintCoroutineRunning = false;

            MeetingSnapshots.Clear();
            UsedHintKeysByImpostor.Clear();
            ExhaustedCategoriesNotified.Clear();
        }

        internal static void CaptureMeetingSnapshot(MeetingHud meetingHud)
        {
            if (!AmongUsClient.Instance.AmHost ||
                !Options.Profiler.GetBool() ||
                !ProfilerSelected ||
                meetingHud == null)
            {
                return;
            }

            int meetingHudId = meetingHud.GetInstanceID();

            // ServerStart e Start possono arrivare molto vicini:
            // la stessa riunione deve essere acquisita una sola volta.
            if (LastCapturedMeetingHudId == meetingHudId)
                return;

            LastCapturedMeetingHudId = meetingHudId;
            MeetingSequence++;
            MeetingSnapshots.Clear();

            List<PlayerControl> impostors = GetAliveImpostors();

            foreach (PlayerControl impostor in impostors)
            {
                MeetingSnapshots[impostor.PlayerId] =
                    CreateSnapshot(impostor);
            }

            BMLogger.Info(
                $"[Profiler] Snapshot meeting {MeetingSequence}: " +
                $"{MeetingSnapshots.Count} impostori vivi.");
        }

        internal static void ScheduleMeetingHint(MeetingHud meetingHud)
        {
            if (!AmongUsClient.Instance.AmHost ||
                !Options.Profiler.GetBool() ||
                !ProfilerSelected ||
                meetingHud == null ||
                HintCoroutineRunning)
            {
                return;
            }

            HintCoroutineRunning = true;
            meetingHud.StartCoroutine(CoSendMeetingHint(meetingHud));
        }

        private static IEnumerator CoSendMeetingHint(MeetingHud meetingHud)
        {
            // Attende che la chat del meeting sia visibile.
            yield return new WaitForSeconds(1.4f);

            HintCoroutineRunning = false;

            if (meetingHud == null ||
                MeetingHud.Instance == null ||
                !AmongUsClient.Instance.AmHost)
            {
                yield break;
            }

            // Se Start è arrivato prima di ServerStart, acquisisce qui.
            CaptureMeetingSnapshot(meetingHud);

            if (MeetingSequence == LastSentMeetingSequence)
                yield break;

            LastSentMeetingSequence = MeetingSequence;
            SendMeetingHint();
        }

        public static void SendMeetingHint()
        {
            if (!AmongUsClient.Instance.AmHost ||
                !Options.Profiler.GetBool() ||
                !ProfilerSelected ||
                ProfilerId == byte.MaxValue)
            {
                return;
            }

            PlayerControl profilerPlayer =
                BanMod.AllPlayerControls.FirstOrDefault(
                    p => p != null && p.PlayerId == ProfilerId);

            if (profilerPlayer == null ||
                profilerPlayer.Data == null ||
                profilerPlayer.Data.IsDead ||
                profilerPlayer.Data.Disconnected)
            {
                return;
            }

            List<PlayerControl> impostors = GetAliveImpostors();

            if (impostors.Count == 0)
            {
                SendPrivateMessage(
                    "PROFILER\n" +
                    "Non risultano impostori vivi da analizzare.");

                return;
            }

            PlayerControl target = SelectNextImpostor(impostors);

            if (target == null)
                return;

            if (!MeetingSnapshots.TryGetValue(
                    target.PlayerId,
                    out ImpostorSnapshot snapshot))
            {
                snapshot = CreateSnapshot(target);
            }

            float taskPercent = GetTaskCompletionPercent(profilerPlayer);
            ProfilerHintStrength strength =
                ResolveStrength(taskPercent);

            LastImpostorId = target.PlayerId;

            HintCandidate hint =
                BuildHint(
                    snapshot,
                    strength,
                    impostors.Count,
                    target.PlayerId);

            if (hint == null || string.IsNullOrWhiteSpace(hint.Text))
            {
                SendHintsExhaustedMessage(
                    target.PlayerId,
                    strength,
                    taskPercent);

                return;
            }

            HashSet<string> usedKeys =
                GetUsedHintKeys(target.PlayerId);

            usedKeys.Add(hint.Key);
            LastHintKey = hint.Key;

            // Se in precedenza la categoria risultava terminata, ma è
            // comparso un nuovo indizio valido, consente una futura notifica.
            ExhaustedCategoriesNotified.Remove(
                GetExhaustedCategoryKey(
                    target.PlayerId,
                    strength));

            string strengthName = GetStrengthName(strength);
            string taskLine = ConfiguredStrength == ProfilerHintStrength.TaskBased
                ? $"\nProgressione task: {Mathf.FloorToInt(taskPercent)}%."
                : string.Empty;

            string message =
                "PROFILER\n" +
                $"Indizio {strengthName}: {hint.Text}" +
                taskLine;

            SendPrivateMessage(message);

            BMLogger.Info(
                $"[Profiler] Meeting {MeetingSequence}, " +
                $"target {target.PlayerId}, forza {strength}, " +
                $"indizio {hint.Key}. " +
                $"Usati per questo impostore: {usedKeys.Count}.");
        }

        private static void SendPrivateMessage(string message)
        {
            if (PlayerControl.LocalPlayer != null &&
                PlayerControl.LocalPlayer.Data != null &&
                PlayerControl.LocalPlayer.Data.IsDead)
            {
                Utils.RequestProxyMessage(message, ProfilerId);
            }
            else
            {
                Utils.SendMessage(message, ProfilerId);
            }

            MessageBlocker.UpdateLastMessageTime();
        }

        private static PlayerControl SelectNextImpostor(
            List<PlayerControl> impostors)
        {
            if (impostors == null || impostors.Count == 0)
                return null;

            List<PlayerControl> ordered = impostors
                .OrderBy(p => p.PlayerId)
                .ToList();

            if (ordered.Count == 1)
                return ordered[0];

            int previousIndex =
                ordered.FindIndex(p => p.PlayerId == LastImpostorId);

            if (previousIndex < 0)
            {
                return ordered[
                    UnityEngine.Random.Range(0, ordered.Count)];
            }

            return ordered[(previousIndex + 1) % ordered.Count];
        }

        private static ProfilerHintStrength ResolveStrength(
            float taskPercent)
        {
            ProfilerHintStrength configured = ConfiguredStrength;

            if (configured != ProfilerHintStrength.TaskBased)
                return configured;

            if (taskPercent < 50f)
                return ProfilerHintStrength.Mild;

            if (taskPercent < 80f)
                return ProfilerHintStrength.Medium;

            return ProfilerHintStrength.Strong;
        }

        private static HintCandidate BuildHint(
            ImpostorSnapshot snapshot,
            ProfilerHintStrength strength,
            int impostorCount,
            byte impostorId)
        {
            string subject = impostorCount > 1
                ? "Uno degli impostori"
                : "L'impostore";

            List<HintCandidate> candidates;

            switch (strength)
            {
                case ProfilerHintStrength.Medium:
                    candidates = BuildMediumHints(snapshot, subject);
                    break;

                case ProfilerHintStrength.Strong:
                    candidates = BuildStrongHints(snapshot, subject);
                    break;

                default:
                    candidates = BuildMildHints(snapshot, subject);
                    break;
            }

            HashSet<string> usedKeys =
                GetUsedHintKeys(impostorId);

            candidates = candidates
                .Where(h =>
                    h != null &&
                    !string.IsNullOrWhiteSpace(h.Key) &&
                    !string.IsNullOrWhiteSpace(h.Text) &&
                    !usedKeys.Contains(h.Key))
                .ToList();

            if (candidates.Count == 0)
                return null;

            return candidates[
                UnityEngine.Random.Range(0, candidates.Count)];
        }

        private static HashSet<string> GetUsedHintKeys(
            byte impostorId)
        {
            if (!UsedHintKeysByImpostor.TryGetValue(
                    impostorId,
                    out HashSet<string> usedKeys))
            {
                usedKeys = new HashSet<string>();
                UsedHintKeysByImpostor[impostorId] = usedKeys;
            }

            return usedKeys;
        }

        private static string GetExhaustedCategoryKey(
            byte impostorId,
            ProfilerHintStrength strength)
        {
            return $"{impostorId}:{(int)strength}";
        }

        private static void SendHintsExhaustedMessage(
            byte impostorId,
            ProfilerHintStrength strength,
            float taskPercent)
        {
            string exhaustedKey =
                GetExhaustedCategoryKey(
                    impostorId,
                    strength);

            // Non ripete a ogni meeting lo stesso avviso di categoria finita.
            if (!ExhaustedCategoriesNotified.Add(exhaustedKey))
                return;

            string message;

            if (ConfiguredStrength == ProfilerHintStrength.TaskBased)
            {
                switch (strength)
                {
                    case ProfilerHintStrength.Mild:
                        message =
                            "PROFILER\n" +
                            "Indizi lievi finiti per questo impostore. " +
                            "Completa più task per ottenere indizi medi.";
                        break;

                    case ProfilerHintStrength.Medium:
                        message =
                            "PROFILER\n" +
                            "Indizi medi finiti per questo impostore. " +
                            "Completa più task per ottenere indizi forti.";
                        break;

                    default:
                        message =
                            "PROFILER\n" +
                            "Indizi forti finiti per questo impostore.";
                        break;
                }

                message +=
                    $"\nProgressione task: " +
                    $"{Mathf.FloorToInt(taskPercent)}%.";
            }
            else
            {
                message =
                    "PROFILER\n" +
                    $"Indizi {GetStrengthName(strength)} finiti " +
                    "per questo impostore.";
            }

            SendPrivateMessage(message);

            BMLogger.Info(
                $"[Profiler] Indizi esauriti | " +
                $"target {impostorId}, forza {strength}.");
        }

        private static List<HintCandidate> BuildMildHints(
            ImpostorSnapshot snapshot,
            string subject)
        {
            var hints = new List<HintCandidate>();
            string name = snapshot.PlayerName ?? string.Empty;

            char[] vowels = name
                .ToLowerInvariant()
                .Where(c => "aeiou".Contains(c))
                .Distinct()
                .ToArray();

            foreach (char vowel in vowels)
            {
                hints.Add(new HintCandidate
                {
                    Key = $"mild-letter-{vowel}",
                    Text = $"{subject} ha la lettera '{vowel}' nel nome."
                });
            }

            if (name.Length > 0)
            {
                hints.Add(new HintCandidate
                {
                    Key = "mild-name-parity",
                    Text = name.Length % 2 == 0
                        ? $"{subject} ha un nome con un numero pari di caratteri."
                        : $"{subject} ha un nome con un numero dispari di caratteri."
                });

                char first = char.ToUpperInvariant(name[0]);

                if (char.IsLetter(first))
                {
                    hints.Add(new HintCandidate
                    {
                        Key = "mild-name-half",
                        Text = first <= 'M'
                            ? $"{subject} ha un nome che inizia tra A e M."
                            : $"{subject} ha un nome che inizia tra N e Z."
                    });
                }

                bool containsNumber = name.Any(char.IsDigit);

                hints.Add(new HintCandidate
                {
                    Key = "mild-name-number",
                    Text = containsNumber
                        ? $"{subject} ha almeno un numero nel nome."
                        : $"{subject} non ha numeri nel nome."
                });
            }

            int broadLevel = GetBroadLevelThreshold(snapshot.Level);

            if (snapshot.Level >= broadLevel)
            {
                hints.Add(new HintCandidate
                {
                    Key = "mild-level-above",
                    Text = $"{subject} ha un livello almeno pari a {broadLevel}."
                });
            }
            else
            {
                hints.Add(new HintCandidate
                {
                    Key = "mild-level-below",
                    Text = $"{subject} ha un livello inferiore a {broadLevel}."
                });
            }

            string mapSide = GetMapSide(snapshot.Position);

            hints.Add(new HintCandidate
            {
                Key = "mild-map-side",
                Text = $"{subject}, al momento della riunione, " +
                       $"si trovava nella zona {mapSide} della mappa."
            });

            return hints;
        }

        private static List<HintCandidate> BuildMediumHints(
            ImpostorSnapshot snapshot,
            string subject)
        {
            var hints = new List<HintCandidate>
            {
                new HintCandidate
                {
                    Key = "medium-pet",
                    Text = snapshot.HasPet
                        ? $"{subject} ha un pet."
                        : $"{subject} non ha un pet."
                },
                new HintCandidate
                {
                    Key = "medium-hat",
                    Text = snapshot.HasHat
                        ? $"{subject} indossa un cappello o copricapo."
                        : $"{subject} non indossa cappelli o copricapi."
                }
            };

            int lower = Mathf.Max(0, (snapshot.Level / 25) * 25);
            int upper = lower + 24;

            hints.Add(new HintCandidate
            {
                Key = "medium-level-range",
                Text = $"{subject} ha un livello compreso tra " +
                       $"{lower} e {upper}."
            });

            string name = snapshot.PlayerName ?? string.Empty;

            if (name.Length > 0 && char.IsLetter(name[0]))
            {
                bool startsWithVowel =
                    "aeiou".Contains(
                        char.ToLowerInvariant(name[0]));

                hints.Add(new HintCandidate
                {
                    Key = "medium-first-type",
                    Text = startsWithVowel
                        ? $"{subject} ha un nome che inizia per vocale."
                        : $"{subject} ha un nome che inizia per consonante."
                });
            }

            if (!string.IsNullOrWhiteSpace(snapshot.RoomName))
            {
                hints.Add(new HintCandidate
                {
                    Key = "medium-room-group",
                    Text = $"{subject}, al momento della riunione, " +
                           $"era nell'area di {GetRoomGroup(snapshot.RoomName)}."
                });
            }

            return hints;
        }

        private static List<HintCandidate> BuildStrongHints(
            ImpostorSnapshot snapshot,
            string subject)
        {
            var hints = new List<HintCandidate>();
            string name = snapshot.PlayerName ?? string.Empty;

            if (!string.IsNullOrWhiteSpace(snapshot.RoomName))
            {
                hints.Add(new HintCandidate
                {
                    Key = "strong-room",
                    Text = $"{subject}, al momento della riunione, " +
                           $"si trovava in {snapshot.RoomName}."
                });
            }
            else
            {
                hints.Add(new HintCandidate
                {
                    Key = "strong-map-side",
                    Text = $"{subject}, al momento della riunione, " +
                           $"si trovava nella zona " +
                           $"{GetMapSide(snapshot.Position)} della mappa."
                });
            }

            if (name.Length > 0)
            {
                char first = char.ToUpperInvariant(name[0]);

                if (char.IsLetterOrDigit(first))
                {
                    hints.Add(new HintCandidate
                    {
                        Key = "strong-first-letter",
                        Text = $"{subject} ha un nome che inizia con '{first}'."
                    });
                }

                hints.Add(new HintCandidate
                {
                    Key = "strong-name-length",
                    Text = $"{subject} ha un nome lungo esattamente " +
                           $"{name.Length} caratteri."
                });
            }

            int lower = Mathf.Max(0, (snapshot.Level / 10) * 10);
            int upper = lower + 9;

            hints.Add(new HintCandidate
            {
                Key = "strong-level-range",
                Text = $"{subject} ha un livello compreso tra " +
                       $"{lower} e {upper}."
            });

            string colorGroup = GetColorGroup(snapshot.ColorId);

            if (!string.IsNullOrWhiteSpace(colorGroup))
            {
                hints.Add(new HintCandidate
                {
                    Key = "strong-color-group",
                    Text = $"{subject} usa un colore della famiglia " +
                           $"{colorGroup}."
                });
            }

            hints.Add(new HintCandidate
            {
                Key = "strong-combined-cosmetic",
                Text = snapshot.HasPet
                    ? $"{subject} ha un pet e " +
                      (snapshot.HasHat
                          ? "indossa anche un cappello."
                          : "non indossa un cappello.")
                    : $"{subject} non ha un pet e " +
                      (snapshot.HasHat
                          ? "indossa un cappello."
                          : "non indossa un cappello.")
            });

            return hints;
        }

        private static int GetBroadLevelThreshold(int level)
        {
            if (level >= 100)
                return 100;

            if (level >= 75)
                return 75;

            if (level >= 50)
                return 50;

            if (level >= 25)
                return 25;

            return 10;
        }

        private static string GetMapSide(Vector2 position)
        {
            if (position.x < -3f)
                return "sinistra";

            if (position.x > 3f)
                return "destra";

            return "centrale";
        }

        private static string GetRoomGroup(string roomName)
        {
            string lower = roomName.ToLowerInvariant();

            if (lower.Contains("mensa") ||
                lower.Contains("cafeteria") ||
                lower.Contains("admin") ||
                lower.Contains("ufficio") ||
                lower.Contains("office"))
            {
                return "amministrazione e sale centrali";
            }

            if (lower.Contains("motore") ||
                lower.Contains("engine") ||
                lower.Contains("reattore") ||
                lower.Contains("reactor") ||
                lower.Contains("elettrica") ||
                lower.Contains("electrical"))
            {
                return "motori e impianti tecnici";
            }

            if (lower.Contains("med") ||
                lower.Contains("laboratorio") ||
                lower.Contains("laboratory") ||
                lower.Contains("specimen"))
            {
                return "medica e laboratori";
            }

            if (lower.Contains("navig") ||
                lower.Contains("armi") ||
                lower.Contains("weapons") ||
                lower.Contains("scudi") ||
                lower.Contains("shields"))
            {
                return "navigazione e sistemi esterni";
            }

            return roomName;
        }

        private static string GetColorGroup(int colorId)
        {
            switch (colorId)
            {
                case 0:
                case 3:
                case 4:
                case 5:
                case 12:
                case 13:
                case 14:
                case 17:
                    return "calda";

                case 1:
                case 2:
                case 8:
                case 10:
                case 11:
                    return "fredda";

                case 6:
                case 9:
                case 15:
                case 16:
                    return "scura o neutra";

                case 7:
                    return "chiara";

                default:
                    return string.Empty;
            }
        }

        private static string GetStrengthName(
            ProfilerHintStrength strength)
        {
            switch (strength)
            {
                case ProfilerHintStrength.Medium:
                    return "medio";

                case ProfilerHintStrength.Strong:
                    return "forte";

                default:
                    return "lieve";
            }
        }

        private static float GetTaskCompletionPercent(
            PlayerControl player)
        {
            if (player == null || player.Data == null)
                return 0f;

            object tasksObject = ReadMember(
                player.Data,
                "Tasks",
                "tasks");

            if (!(tasksObject is IEnumerable enumerable))
                return 0f;

            int total = 0;
            int completed = 0;

            foreach (object task in enumerable)
            {
                if (task == null)
                    continue;

                total++;

                object completeValue =
                    ReadMember(task, "Complete", "complete");

                if (ConvertToBool(completeValue))
                    completed++;
            }

            if (total <= 0)
                return 0f;

            return completed * 100f / total;
        }

        private static List<PlayerControl> GetAliveImpostors()
        {
            return BanMod.AllPlayerControls
                .Where(p => p != null
                            && p.Data != null
                            && !p.Data.IsDead
                            && !p.Data.Disconnected
                            && IsImpostorAligned(p))
                .OrderBy(p => p.PlayerId)
                .ToList();
        }

        private static bool IsImpostorAligned(
            PlayerControl player)
        {
            if (player == null || player.Data == null)
                return false;

            try
            {
                if (BanMod.forceImpostor &&
                    BanMod.forcedImpostorIds.Contains(player.PlayerId))
                {
                    return true;
                }
            }
            catch
            {
                // Ignora se la modalità forced impostor non è inizializzata.
            }

            try
            {
                if (Impostor(player) ||
                    Cobra(player) ||
                    Shapeshifter(player) ||
                    Phantom(player))
                {
                    return true;
                }
            }
            catch
            {
                // Fallback tramite Role.IsImpostor.
            }

            object role = ReadMember(player.Data, "Role", "role");
            object isImpostor =
                ReadMember(role, "IsImpostor", "isImpostor");

            return ConvertToBool(isImpostor);
        }

        private static ImpostorSnapshot CreateSnapshot(
            PlayerControl player)
        {
            object data = player?.Data;
            object outfit = ReadMember(
                data,
                "DefaultOutfit",
                "defaultOutfit");

            return new ImpostorSnapshot
            {
                PlayerId = player.PlayerId,
                PlayerName = player.Data?.PlayerName ?? string.Empty,
                Level = ConvertToInt(
                    ReadMember(data, "PlayerLevel", "playerLevel")),
                HasPet = HasCosmetic(
                    ReadMember(outfit, "PetId", "petId")),
                HasHat = HasCosmetic(
                    ReadMember(outfit, "HatId", "hatId")),
                ColorId = ConvertToInt(
                    ReadMember(outfit, "ColorId", "colorId"),
                    -1),
                Position = player != null
                    ? (Vector2)player.transform.position
                    : Vector2.zero,
                RoomName = GetPlayerRoomName(player)
            };
        }

        private static bool HasCosmetic(object cosmeticId)
        {
            string value = cosmeticId?.ToString();

            if (string.IsNullOrWhiteSpace(value))
                return false;

            string lower = value.ToLowerInvariant();

            return !lower.Contains("empty") &&
                   !lower.Contains("none") &&
                   lower != "0";
        }

        private static string GetPlayerRoomName(
            PlayerControl player)
        {
            if (player == null || ShipStatus.Instance == null)
                return string.Empty;

            Vector2 position = player.transform.position;

            object roomsObject = ReadMember(
                ShipStatus.Instance,
                "AllRooms",
                "allRooms");

            if (!(roomsObject is IEnumerable rooms))
                return string.Empty;

            foreach (object room in rooms)
            {
                if (room == null)
                    continue;

                object areaObject = ReadMember(
                    room,
                    "roomArea",
                    "RoomArea",
                    "roomCollider",
                    "RoomCollider");

                Collider2D collider = areaObject as Collider2D;

                if (collider == null &&
                    areaObject is Component areaComponent)
                {
                    collider =
                        areaComponent.GetComponent<Collider2D>();
                }

                if (collider == null &&
                    room is Component roomComponent)
                {
                    collider =
                        roomComponent.GetComponent<Collider2D>();
                }

                if (collider == null ||
                    !collider.OverlapPoint(position))
                {
                    continue;
                }

                object roomId = ReadMember(
                    room,
                    "RoomId",
                    "roomId");

                string rawName =
                    roomId?.ToString() ?? room.ToString();

                return TranslateRoomName(rawName);
            }

            return string.Empty;
        }

        private static string TranslateRoomName(string rawName)
        {
            if (string.IsNullOrWhiteSpace(rawName))
                return string.Empty;

            switch (rawName.Replace(" ", string.Empty)
                           .ToLowerInvariant())
            {
                case "cafeteria":
                    return "Mensa";
                case "weapons":
                    return "Armi";
                case "navigation":
                    return "Navigazione";
                case "o2":
                case "lifesupp":
                    return "O2";
                case "shields":
                    return "Scudi";
                case "communications":
                case "comms":
                    return "Comunicazioni";
                case "storage":
                    return "Magazzino";
                case "admin":
                    return "Amministrazione";
                case "electrical":
                    return "Elettrica";
                case "lowerengine":
                    return "Motore inferiore";
                case "upperengine":
                    return "Motore superiore";
                case "security":
                    return "Sicurezza";
                case "reactor":
                    return "Reattore";
                case "medbay":
                    return "Infermeria";
                case "laboratory":
                    return "Laboratorio";
                case "specimenroom":
                    return "Sala campioni";
                case "office":
                    return "Ufficio";
                case "greenhouse":
                    return "Serra";
                case "launchpad":
                    return "Piattaforma di lancio";
                case "balcony":
                    return "Balcone";
                case "records":
                    return "Archivi";
                case "vaultroom":
                case "vault":
                    return "Caveau";
                case "brig":
                    return "Brigata";
                case "engine":
                    return "Sala motori";
                case "mainhall":
                    return "Sala principale";
                case "showers":
                    return "Docce";
                case "meetingroom":
                    return "Sala riunioni";
                case "kitchen":
                    return "Cucina";
                case "cockpit":
                    return "Cabina di pilotaggio";
                case "armory":
                    return "Armeria";
                case "viewingdeck":
                    return "Ponte panoramico";
                case "medical":
                    return "Medica";
                case "securityroom":
                    return "Sicurezza";
                default:
                    return rawName;
            }
        }

        private static object ReadMember(
            object source,
            params string[] names)
        {
            if (source == null || names == null)
                return null;

            Type type = source.GetType();
            const BindingFlags flags =
                BindingFlags.Instance |
                BindingFlags.Public |
                BindingFlags.NonPublic |
                BindingFlags.IgnoreCase;

            foreach (string name in names)
            {
                try
                {
                    PropertyInfo property =
                        type.GetProperty(name, flags);

                    if (property != null)
                        return property.GetValue(source);
                }
                catch
                {
                    // Prova il campo.
                }

                try
                {
                    FieldInfo field =
                        type.GetField(name, flags);

                    if (field != null)
                        return field.GetValue(source);
                }
                catch
                {
                    // Prova il nome successivo.
                }
            }

            return null;
        }

        private static int ConvertToInt(
            object value,
            int fallback = 0)
        {
            if (value == null)
                return fallback;

            try
            {
                return Convert.ToInt32(value);
            }
            catch
            {
                return fallback;
            }
        }

        private static bool ConvertToBool(object value)
        {
            if (value == null)
                return false;

            try
            {
                return Convert.ToBoolean(value);
            }
            catch
            {
                return false;
            }
        }
    }

    [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.ServerStart))]
    public static class ProfilerMeetingCapturePatch
    {
        public static void Prefix(MeetingHud __instance)
        {
            Profiler.CaptureMeetingSnapshot(__instance);
        }
    }

    [HarmonyPatch(typeof(MeetingHud), "Start")]
    public static class ProfilerMeetingMessagePatch
    {
        public static void Postfix(MeetingHud __instance)
        {
            Profiler.ScheduleMeetingHint(__instance);
        }
    }
}
