// Assets/_Project/Scripts/UI/RaceSelectionOverlay.cs
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;
using TD.Core;
using TD.Gameplay;

namespace TD.UI
{
    /// <summary>
    /// Lobby-scene overlay that lets the local player pick their race.
    /// Shows a 4x4 grid of race icons + a detail panel that pops up to the
    /// right when an icon is clicked. Reads other players' picks from
    /// <see cref="PlayerMatchState.AllPlayers"/> each frame to grey out
    /// races that have already been taken.
    /// </summary>
    /// <remarks>
    /// <para><b>Wiring.</b> Attach this component to the same GameObject as
    /// <c>LobbyController</c>. The controller calls <see cref="Initialize"/>
    /// once with its root <see cref="VisualElement"/>, and <see cref="Show"/>
    /// when the "Select Race" button is clicked.</para>
    ///
    /// <para><b>Why same UIDocument.</b> The overlay's elements are inserted as
    /// absolute-positioned children of the lobby root — that way one UIDocument
    /// + one PanelSettings serves both the lobby and the overlay, and z-order is
    /// natural (overlay last → on top).</para>
    ///
    /// <para><b>Visual states per grid cell.</b>
    /// <list type="bullet">
    ///   <item><b>Free</b> — race not picked by anyone. Icon in color, clickable.</item>
    ///   <item><b>Taken by another player</b> — greyed out, clickable for viewing
    ///         the detail panel but confirm button is disabled. Player slot
    ///         number overlaid in color so others know who claimed it.</item>
    ///   <item><b>Taken by local player</b> — bordered highlight, clickable.
    ///         Confirm button enabled (no-op if already selected, but lets the
    ///         player re-confirm). Local slot number overlaid.</item>
    ///   <item><b>Unregistered slot</b> — placeholder "Coming Soon", not
    ///         clickable. No RaceDefinition asset exists for this RaceId yet.</item>
    /// </list></para>
    /// </remarks>
    public class RaceSelectionOverlay : MonoBehaviour
    {
        // ----- UI state ---------------------------------------------------

        private VisualElement overlayRoot;
        private VisualElement gridContainer;
        private VisualElement detailPanel;
        private Label         detailHeader;
        private VisualElement detailIcon;
        private Label         detailBuilderName;
        private Label         detailBuilderDesc;
        private Label         detailLore;
        private Button        detailConfirmButton;
        private Label         detailUnavailableNote;

        // Currently-viewed race in the detail panel. RaceId.None means no
        // detail is open and the panel shows a hint instead.
        private RaceId viewedRace = RaceId.None;

        // Re-built each Update from PlayerMatchState data. Maps each taken
        // RaceId to the slot of the player that picked it.
        private readonly Dictionary<RaceId, PlayerSlot> picksByRace = new Dictionary<RaceId, PlayerSlot>();

        // Snapshot of pick-state from the last grid rebuild. RefreshGrid skips
        // rebuilding when this matches the current frame — same click-eating
        // problem as the lobby's player list. Cleared on Show() so the grid
        // always rebuilds when the overlay reopens.
        private string lastGridSignature = string.Empty;
        private readonly StringBuilder signatureBuffer = new StringBuilder();

        // ----- Public API -------------------------------------------------

        public bool IsVisible =>
            overlayRoot != null && overlayRoot.style.display.value == DisplayStyle.Flex;

        /// <summary>
        /// Called by LobbyController once to attach the overlay's UI to the
        /// lobby's UIDocument root. Builds the elements (hidden) and is then
        /// reused across Show / Hide cycles.
        /// </summary>
        public void Initialize(VisualElement lobbyRoot)
        {
            if (lobbyRoot == null)
            {
                Debug.LogError("[RaceSelectionOverlay] Initialize received null lobby root.");
                return;
            }
            BuildUI(lobbyRoot);
            Hide();
        }

        /// <summary>
        /// Reveals the overlay, refreshes the grid against current picks, and
        /// auto-opens the detail panel for the local player's currently-picked
        /// race (if any) so they can change it directly.
        /// </summary>
        public void Show()
        {
            if (overlayRoot == null) return;
            overlayRoot.style.display = DisplayStyle.Flex;

            // If the local player already has a race, seed the detail panel with
            // it. Otherwise leave the panel showing the placeholder hint.
            var local = PlayerMatchState.Local;
            viewedRace = (local != null) ? local.RaceSelection : RaceId.None;

            // Force a fresh grid build on each open so visuals reflect any
            // picks that happened while the overlay was closed.
            lastGridSignature = string.Empty;
            RefreshGrid();
            RefreshDetail();
        }

        public void Hide()
        {
            if (overlayRoot == null) return;
            overlayRoot.style.display = DisplayStyle.None;
            viewedRace = RaceId.None;
        }

        // ----- Lifecycle --------------------------------------------------

        private void Update()
        {
            if (!IsVisible) return;

            // Esc closes the overlay. Read directly via Input System — we want
            // this even when nothing is focused.
            var kb = Keyboard.current;
            if (kb != null && kb.escapeKey.wasPressedThisFrame)
            {
                Hide();
                return;
            }

            // Refresh against current picks every frame — cheap (max 16 cells,
            // max 9 players) and means other-player changes show up immediately.
            RefreshGrid();
            RefreshDetail();
        }

        // ----- UI construction --------------------------------------------

        private void BuildUI(VisualElement lobbyRoot)
        {
            // Root overlay — fills the screen with a dark backdrop.
            overlayRoot = new VisualElement();
            overlayRoot.pickingMode        = PickingMode.Position;
            overlayRoot.style.position     = Position.Absolute;
            overlayRoot.style.left         = 0;
            overlayRoot.style.right        = 0;
            overlayRoot.style.top          = 0;
            overlayRoot.style.bottom       = 0;
            overlayRoot.style.backgroundColor = new Color(0f, 0f, 0f, 0.85f);
            overlayRoot.style.alignItems   = Align.Center;
            overlayRoot.style.justifyContent = Justify.Center;
            lobbyRoot.Add(overlayRoot);

            // Title + close button in a top bar.
            var topBar = new VisualElement();
            topBar.style.position        = Position.Absolute;
            topBar.style.top             = 20;
            topBar.style.left            = 40;
            topBar.style.right           = 40;
            topBar.style.flexDirection   = FlexDirection.Row;
            topBar.style.justifyContent  = Justify.SpaceBetween;
            topBar.style.alignItems      = Align.Center;
            overlayRoot.Add(topBar);

            var title = new Label("Select Race");
            title.style.fontSize        = 32;
            title.style.color           = Color.white;
            title.style.unityFontStyleAndWeight = FontStyle.Bold;
            topBar.Add(title);

            var closeBtn = new Button(Hide) { text = "X" };
            closeBtn.style.width    = 44;
            closeBtn.style.height   = 44;
            closeBtn.style.fontSize = 20;
            topBar.Add(closeBtn);

            // Main content row: grid on the left, detail panel on the right.
            var content = new VisualElement();
            content.style.flexDirection = FlexDirection.Row;
            content.style.alignItems    = Align.Stretch;
            content.style.marginTop     = 40;
            overlayRoot.Add(content);

            // Grid container — 4x4 of cells. Uses wrap to flow rows.
            gridContainer = new VisualElement();
            gridContainer.style.flexDirection = FlexDirection.Row;
            gridContainer.style.flexWrap      = Wrap.Wrap;
            gridContainer.style.width         = 560;          // 4 cells * (120 + 8 margin) = 512 + slack
            gridContainer.style.marginRight   = 32;
            content.Add(gridContainer);

            // Detail panel — built once, populated in RefreshDetail.
            detailPanel = new VisualElement();
            detailPanel.style.width            = 380;
            detailPanel.style.minHeight        = 520;
            detailPanel.style.paddingTop       = 20;
            detailPanel.style.paddingBottom    = 20;
            detailPanel.style.paddingLeft      = 20;
            detailPanel.style.paddingRight     = 20;
            detailPanel.style.backgroundColor  = new Color(0.10f, 0.10f, 0.14f, 0.95f);
            detailPanel.style.borderTopWidth   = detailPanel.style.borderBottomWidth =
                detailPanel.style.borderLeftWidth = detailPanel.style.borderRightWidth = 2;
            var detailBorder = new Color(0.4f, 0.4f, 0.5f);
            detailPanel.style.borderTopColor   = detailPanel.style.borderBottomColor =
                detailPanel.style.borderLeftColor = detailPanel.style.borderRightColor = detailBorder;
            detailPanel.style.flexDirection    = FlexDirection.Column;
            content.Add(detailPanel);

            detailHeader = new Label("Pick a race");
            detailHeader.style.fontSize    = 22;
            detailHeader.style.color       = Color.white;
            detailHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
            detailHeader.style.marginBottom = 12;
            detailPanel.Add(detailHeader);

            detailIcon = new VisualElement();
            detailIcon.style.width           = 200;
            detailIcon.style.height          = 200;
            detailIcon.style.alignSelf       = Align.Center;
            detailIcon.style.backgroundColor = new Color(0.18f, 0.18f, 0.22f);
            detailIcon.style.marginBottom    = 12;
            detailPanel.Add(detailIcon);

            detailBuilderName = new Label();
            detailBuilderName.style.fontSize    = 16;
            detailBuilderName.style.color       = new Color(0.95f, 0.85f, 0.5f);
            detailBuilderName.style.unityFontStyleAndWeight = FontStyle.Bold;
            detailPanel.Add(detailBuilderName);

            detailBuilderDesc = new Label();
            detailBuilderDesc.style.fontSize     = 13;
            detailBuilderDesc.style.color        = new Color(0.85f, 0.85f, 0.85f);
            detailBuilderDesc.style.marginBottom = 10;
            detailBuilderDesc.style.whiteSpace   = WhiteSpace.Normal;
            detailPanel.Add(detailBuilderDesc);

            detailLore = new Label();
            detailLore.style.fontSize    = 12;
            detailLore.style.color       = new Color(0.75f, 0.75f, 0.75f);
            detailLore.style.marginBottom = 16;
            detailLore.style.whiteSpace  = WhiteSpace.Normal;
            detailLore.style.flexGrow    = 1;
            detailPanel.Add(detailLore);

            // Notice shown when the viewed race is taken by another player.
            detailUnavailableNote = new Label();
            detailUnavailableNote.style.color    = new Color(1f, 0.5f, 0.3f);
            detailUnavailableNote.style.fontSize = 12;
            detailUnavailableNote.style.marginBottom = 8;
            detailUnavailableNote.style.whiteSpace = WhiteSpace.Normal;
            detailPanel.Add(detailUnavailableNote);

            detailConfirmButton = new Button(OnConfirmClicked) { text = "Confirm Selection" };
            detailConfirmButton.style.height      = 40;
            detailConfirmButton.style.fontSize    = 16;
            detailPanel.Add(detailConfirmButton);
        }

        // ----- Per-frame refresh ------------------------------------------

        // Walks every PlayerMatchState and records which RaceId each non-None
        // pick belongs to. Used by both the grid (grey out taken) and the
        // detail panel (gate the confirm button).
        private void RebuildPickIndex()
        {
            picksByRace.Clear();
            foreach (var pms in PlayerMatchState.AllPlayers)
            {
                if (pms.RaceSelection == RaceId.None) continue;
                picksByRace[pms.RaceSelection] = pms.Slot;
            }
        }

        private void RefreshGrid()
        {
            if (gridContainer == null) return;
            if (RaceRegistry.Instance == null) return;

            RebuildPickIndex();

            // Skip rebuild when nothing changed — keeps the cell event handlers
            // alive across frames so clicks aren't eaten mid-press.
            PlayerSlot localSlot = PlayerMatchState.Local != null
                ? PlayerMatchState.Local.Slot
                : PlayerSlot.None;
            string signature = ComputeGridSignature(localSlot);
            if (signature == lastGridSignature) return;
            lastGridSignature = signature;

            gridContainer.Clear();
            foreach (var (id, def) in RaceRegistry.Instance.AllSlots())
            {
                gridContainer.Add(BuildCell(id, def, localSlot));
            }
        }

        // Compact signature of every input the grid cells depend on: the
        // current pick map (RaceId → PlayerSlot) plus the local player's slot
        // (which affects "is this mine" highlighting on each cell).
        private string ComputeGridSignature(PlayerSlot localSlot)
        {
            signatureBuffer.Clear();
            signatureBuffer.Append((int)localSlot);
            signatureBuffer.Append('|');
            foreach (var kvp in picksByRace.OrderBy(p => (int)p.Key))
            {
                signatureBuffer.Append((int)kvp.Key);
                signatureBuffer.Append(':');
                signatureBuffer.Append((int)kvp.Value);
                signatureBuffer.Append(';');
            }
            return signatureBuffer.ToString();
        }

        private VisualElement BuildCell(RaceId id, RaceDefinition def, PlayerSlot localSlot)
        {
            // Outer cell with fixed size for consistent grid layout.
            var cell = new VisualElement();
            cell.style.width   = 120;
            cell.style.height  = 120;
            cell.style.marginTop = cell.style.marginBottom =
                cell.style.marginLeft = cell.style.marginRight = 4;
            cell.style.borderTopWidth = cell.style.borderBottomWidth =
                cell.style.borderLeftWidth = cell.style.borderRightWidth = 2;

            bool isLocked = (def == null);
            bool isPicked = picksByRace.TryGetValue(id, out var picker);
            bool isMine   = isPicked && picker == localSlot;
            bool isTakenByOther = isPicked && !isMine;

            // Border color signals state at a glance.
            Color borderColor = isMine
                ? new Color(0.3f, 0.85f, 0.3f)              // green for "mine"
                : isTakenByOther
                    ? new Color(0.6f, 0.2f, 0.2f)           // red-ish for taken
                    : isLocked
                        ? new Color(0.3f, 0.3f, 0.3f)       // dim for placeholder
                        : new Color(0.5f, 0.5f, 0.6f);      // neutral for free
            cell.style.borderTopColor    = cell.style.borderBottomColor =
                cell.style.borderLeftColor = cell.style.borderRightColor = borderColor;
            cell.style.backgroundColor   = new Color(0.10f, 0.10f, 0.14f);

            // Icon area (or placeholder text for locked slots).
            var iconHolder = new VisualElement();
            iconHolder.pickingMode = PickingMode.Ignore;
            iconHolder.style.flexGrow = 1;
            iconHolder.style.alignItems     = Align.Center;
            iconHolder.style.justifyContent = Justify.Center;
            iconHolder.style.unityBackgroundImageTintColor = isTakenByOther
                ? new Color(0.4f, 0.4f, 0.4f)               // greyed out
                : Color.white;
            if (def != null && def.Icon != null)
                iconHolder.style.backgroundImage = new StyleBackground(def.Icon);
            else if (isLocked)
            {
                var placeholder = new Label("?");
                placeholder.style.fontSize = 36;
                placeholder.style.color    = new Color(0.4f, 0.4f, 0.4f);
                iconHolder.Add(placeholder);
            }
            cell.Add(iconHolder);

            // Race name strip at the bottom — visible even when icon is missing.
            var nameLabel = new Label(def != null ? def.DisplayName : "Coming Soon");
            nameLabel.pickingMode = PickingMode.Ignore;
            nameLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
            nameLabel.style.fontSize       = 11;
            nameLabel.style.color          = isLocked ? new Color(0.45f, 0.45f, 0.45f) : Color.white;
            nameLabel.style.paddingTop     = 2;
            nameLabel.style.paddingBottom  = 2;
            nameLabel.style.backgroundColor = new Color(0f, 0f, 0f, 0.55f);
            cell.Add(nameLabel);

            // Picker badge — top-right corner, only when taken. Shows the slot
            // number of whoever picked it.
            if (isPicked)
            {
                var badge = new Label($"P{(int)picker}");
                badge.pickingMode = PickingMode.Ignore;
                badge.style.position = Position.Absolute;
                badge.style.top      = 4;
                badge.style.right    = 4;
                badge.style.fontSize = 16;
                badge.style.unityFontStyleAndWeight = FontStyle.Bold;
                badge.style.color    = isMine
                    ? new Color(0.3f, 0.85f, 0.3f)
                    : new Color(1f, 0.7f, 0.2f);
                badge.style.paddingLeft  = 4;
                badge.style.paddingRight = 4;
                badge.style.backgroundColor = new Color(0f, 0f, 0f, 0.7f);
                cell.Add(badge);
            }

            // Click handler — only enabled for non-locked cells. Locked cells
            // still get the visual treatment but no click response.
            if (!isLocked)
            {
                cell.RegisterCallback<ClickEvent>(_ => OnCellClicked(id));
            }

            return cell;
        }

        // ----- Detail panel -----------------------------------------------

        private void OnCellClicked(RaceId id)
        {
            viewedRace = id;
            RefreshDetail();
        }

        private void RefreshDetail()
        {
            if (detailPanel == null) return;
            var registry = RaceRegistry.Instance;
            if (registry == null) return;

            var def = registry.Get(viewedRace);

            if (def == null)
            {
                // Empty / locked / no selection — show placeholder hint.
                detailHeader.text          = "Pick a race";
                detailBuilderName.text     = string.Empty;
                detailBuilderDesc.text     = string.Empty;
                detailLore.text            = "Click a race icon to see details.";
                detailIcon.style.backgroundImage = null;
                detailUnavailableNote.text = string.Empty;
                detailConfirmButton.SetEnabled(false);
                detailConfirmButton.text   = "Confirm Selection";
                return;
            }

            detailHeader.text       = def.DisplayName;
            detailBuilderName.text  = string.IsNullOrEmpty(def.BuilderName)
                ? "Builder: (unnamed)"
                : $"Builder: {def.BuilderName}";
            detailBuilderDesc.text  = def.BuilderDescription ?? string.Empty;
            detailLore.text         = def.LoreText ?? string.Empty;
            detailIcon.style.backgroundImage = def.Icon != null
                ? new StyleBackground(def.Icon)
                : null;

            // Is this race available to the local player?
            bool isPicked = picksByRace.TryGetValue(viewedRace, out var picker);
            PlayerSlot localSlot = PlayerMatchState.Local != null
                ? PlayerMatchState.Local.Slot
                : PlayerSlot.None;
            bool takenByOther = isPicked && picker != localSlot;
            bool takenByMe    = isPicked && picker == localSlot;

            if (takenByOther)
            {
                detailUnavailableNote.text = $"Already selected by P{(int)picker}.";
                detailConfirmButton.SetEnabled(false);
                detailConfirmButton.text = "Unavailable";
            }
            else if (takenByMe)
            {
                detailUnavailableNote.text = "Your current selection.";
                detailConfirmButton.SetEnabled(true);
                detailConfirmButton.text = "Confirm Selection";
            }
            else
            {
                detailUnavailableNote.text = string.Empty;
                detailConfirmButton.SetEnabled(true);
                detailConfirmButton.text = "Confirm Selection";
            }
        }

        private void OnConfirmClicked()
        {
            var local = PlayerMatchState.Local;
            if (local == null)
            {
                Debug.LogWarning("[RaceSelectionOverlay] No local PlayerMatchState — cannot submit race.");
                return;
            }
            if (viewedRace == RaceId.None) return;

            // Re-check exclusivity right before submitting — between detail open
            // and confirm click, another player may have grabbed it.
            if (picksByRace.TryGetValue(viewedRace, out var picker) && picker != local.Slot)
            {
                Debug.Log($"[RaceSelectionOverlay] Race already taken by P{(int)picker}. " +
                          "Refreshing detail.");
                RefreshDetail();
                return;
            }

            local.SubmitRaceRpc(viewedRace);

            // Per the spec: confirming clears the detail panel but keeps the
            // overlay open. The grid will update to show the new picked state
            // on the next Update tick.
            viewedRace = RaceId.None;
            RefreshDetail();
        }
    }
}
