using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using GeoSus.Client;

/// <summary>
/// Lives on create.unity, the post-creation lobby view. The scene already
/// contains the art team's named UI:
///
///   Canvas
///   ├── Panel              - full-screen background
///   ├── max players        - TMP "Max players: " label (we append the count)
///   ├── lobby code         - TMP "Lobby code: " label (we append the code)
///   ├── smazatbutton       - "delete/leave" button
///   ├── lobby info         - info button (currently no-op)
///   ├── player list neglow - decorative glow behind the player list
///   ├── player list        - container for the player list (originally held
///   │                        a single placeholder text child)
///   ├── tuff jmeno         - hardcoded player slot 1 (placeholder)
///   ├── netuff jmeno       - hardcoded player slot 2 (placeholder)
///   └── stvořit            - "Start Game" button
///
/// We DO NOT destroy any of these. Instead we resolve them by name, wire the
/// buttons, update text labels live, hide the placeholder slots, and inject
/// a ScrollRect inside the existing 'player list' container so any number of
/// players can be displayed.
///
/// GameManager_UI calls RefreshAll(LobbyState) when the lobby state changes;
/// we cache the latest state and apply it on the next Update so all scene
/// references stay on the main thread.
/// </summary>
public class LobbyDisplayUI : MonoBehaviour
{
    // ── Static hub so GameManager_UI can push state updates ──────────────────
    private static readonly HashSet<LobbyDisplayUI> _all = new HashSet<LobbyDisplayUI>();
    public static void RefreshAll(LobbyState state)
    {
        foreach (var ui in _all) ui._pending = state;
    }

    // ── Resolved scene refs ──────────────────────────────────────────────────
    private TMP_Text   _codeLabel;       // "lobby code" - prefix + JoinCode appended
    private TMP_Text   _maxPlayersLabel; // "max players" - prefix + count appended
    private Button     _leaveBtn;        // "smazatbutton"
    private Button     _infoBtn;         // "lobby info"
    private Button     _startBtn;        // "stvořit"
    private RectTransform _playerListRT; // "player list" container - scroll lives inside
    private TMP_Text   _waitingForHostText; // injected; visible to non-host only

    // ── Injected scroll list (added once at Start, populated on every refresh) ─
    private RectTransform _scrollContent;
    private readonly List<GameObject> _rows = new List<GameObject>();

    private LobbyState _pending;

    // ── Colour palette (forwarded from UITheme) ──────────────────────────────
    static readonly Color C_ROW_A   = UITheme.RowA;
    static readonly Color C_ROW_B   = UITheme.RowB;
    static readonly Color C_DIVIDER = UITheme.SurfaceAlt;
    static readonly Color C_ACCENT  = UITheme.Accent;
    static readonly Color C_GOLD    = UITheme.Caution;
    static readonly Color C_WHITE   = UITheme.TextHi;
    static readonly Color C_SOFT    = UITheme.TextMid;

    void OnEnable()  => _all.Add(this);
    void OnDisable() => _all.Remove(this);

    void Start()
    {
        // Wire emoji fallback ASAP so player-list rows with emoji glyphs
        // (host crown, "you" badge, etc.) render correctly on the first
        // refresh.
        UITheme.EnsureEmojiFontFallback();

        var canvasGO = GameObject.Find("Canvas");
        if (canvasGO == null)
        {
            Debug.LogError("[LobbyDisplayUI] No Canvas found in create scene.");
            return;
        }
        var canvas = canvasGO.transform;

        // ── Bind existing scene elements ────────────────────────────────────
        _codeLabel       = FindTMP(canvas, "lobby code");
        _maxPlayersLabel = FindTMP(canvas, "max players");
        _leaveBtn        = FindButton(canvas, "smazatbutton");
        _infoBtn         = FindButton(canvas, "lobby info");
        _startBtn        = FindButton(canvas, "stvořit");
        var listGO       = FindByName(canvas, "player list");
        _playerListRT    = listGO != null ? listGO as RectTransform : null;

        // ── Wire buttons (preserve existing AudioSource OnClick by appending) ──
        if (_leaveBtn != null)
        {
            // Don't clear; the art team's AudioSource.Play hook should still
            // fire alongside the leave action.
            _leaveBtn.onClick.AddListener(() => GameManager.Instance?.LeaveLobbyButton());
        }
        else Debug.LogWarning("[LobbyDisplayUI] 'smazatbutton' not found.");

        if (_infoBtn != null)
        {
            // Tap-to-copy the lobby code to clipboard. Cheap useful action that
            // gives the existing info button real behavior.
            _infoBtn.onClick.AddListener(() =>
            {
                var code = GameManager.Instance?.gameClient?.CurrentLobbyState?.JoinCode;
                if (!string.IsNullOrEmpty(code)) GUIUtility.systemCopyBuffer = code;
            });
        }

        if (_startBtn != null)
        {
            _startBtn.onClick.AddListener(() => GameManager.Instance?.StartGameButton());
        }
        else Debug.LogWarning("[LobbyDisplayUI] 'stvořit' not found.");

        // ── Hide the hardcoded placeholder slots ────────────────────────────
        // These were kept in the scene as visual previews of what filled rows
        // would look like; with a working scrollable list they're redundant.
        var tuff   = FindByName(canvas, "tuff jmeno");
        var netuff = FindByName(canvas, "netuff jmeno");
        if (tuff)   tuff.gameObject.SetActive(false);
        if (netuff) netuff.gameObject.SetActive(false);

        // ── Override the player list's RectTransform to a sensible portrait
        //    layout. The art team's anchored position + size were calibrated
        //    for a different reference resolution and the element only filled
        //    a third of the viewable area at 1080x1920. Stretch it to fill
        //    the central portion of the screen with even insets.
        if (_playerListRT != null)
        {
            _playerListRT.anchorMin = new Vector2(0, 0);
            _playerListRT.anchorMax = new Vector2(1, 1);
            _playerListRT.pivot     = new Vector2(0.5f, 0.5f);
            // Top inset = 320 (room for header / lobby code), bottom inset =
            // 380 (room for the start button or waiting message), 60px sides.
            _playerListRT.offsetMin = new Vector2(60, 380);
            _playerListRT.offsetMax = new Vector2(-60, -320);
            _playerListRT.anchoredPosition = Vector2.zero;
            BuildScrollList(_playerListRT);
        }
        else
        {
            Debug.LogWarning("[LobbyDisplayUI] 'player list' container not found - " +
                "falling back to no list rendering.");
        }

        // Also re-anchor the "player list neglow" decoration to track the new
        // player list region, so the glow doesn't float empty offscreen.
        var neglow = FindByName(canvas, "player list neglow") as RectTransform;
        if (neglow != null)
        {
            neglow.anchorMin = new Vector2(0, 0);
            neglow.anchorMax = new Vector2(1, 1);
            neglow.pivot     = new Vector2(0.5f, 0.5f);
            neglow.offsetMin = new Vector2(40, 360);
            neglow.offsetMax = new Vector2(-40, -300);
        }

        // ── Inject "Waiting for host..." text for non-host players ──────────
        // Visible only when the local player isn't the lobby owner; sits in
        // the same vertical strip as the start button so the screen has a
        // single consistent action zone for both roles.
        var waitGO = MakeRT("WaitingForHost", canvas);
        Anchor(waitGO,
               new Vector2(0.05f, 0), new Vector2(0.95f, 0),
               new Vector2(0, 60), new Vector2(0, 240));
        _waitingForHostText = waitGO.gameObject.AddComponent<TextMeshProUGUI>();
        _waitingForHostText.text = "⌛  Waiting for host to start the game...";
        _waitingForHostText.fontSize = 38;
        _waitingForHostText.color = new Color(0.73f, 0.80f, 0.88f);
        _waitingForHostText.fontStyle = FontStyles.Italic;
        _waitingForHostText.alignment = TextAlignmentOptions.Center;
        _waitingForHostText.gameObject.SetActive(false); // toggled per refresh
    }

    void Update()
    {
        var gm = GameManager.Instance;
        if (gm?.gameClient?.CurrentLobbyState != null)
            _pending = gm.gameClient.CurrentLobbyState;

        if (_pending != null)
        {
            Refresh(_pending);
            _pending = null;
        }
    }

    // ── Scroll list construction ─────────────────────────────────────────────
    void BuildScrollList(RectTransform listRoot)
    {
        // Strip any existing children of `player list` (typically one
        // placeholder TMP). Don't touch the listRoot itself - it has the art
        // team's anchoring + glow pairing we want to preserve.
        var kill = new List<GameObject>();
        foreach (Transform child in listRoot) kill.Add(child.gameObject);
        foreach (var go in kill) DestroyImmediate(go);

        // ScrollRect on the list root
        var sr = listRoot.gameObject.GetComponent<ScrollRect>()
              ?? listRoot.gameObject.AddComponent<ScrollRect>();
        sr.horizontal = false; sr.vertical = true;
        sr.movementType = ScrollRect.MovementType.Elastic;
        sr.elasticity = 0.1f;
        sr.scrollSensitivity = 80f;

        // Viewport (with mask for clipping)
        var viewport = MakeRT("Viewport", listRoot);
        Stretch(viewport);
        var vpImg = viewport.gameObject.AddComponent<Image>();
        vpImg.color = new Color(0,0,0,0);
        viewport.gameObject.AddComponent<RectMask2D>();

        // Content - populated from State.Players
        var content = MakeRT("Content", viewport);
        content.anchorMin = new Vector2(0, 1);
        content.anchorMax = new Vector2(1, 1);
        content.pivot     = new Vector2(0.5f, 1);
        content.sizeDelta = Vector2.zero;
        content.anchoredPosition = Vector2.zero;
        var vlg = content.gameObject.AddComponent<VerticalLayoutGroup>();
        vlg.childControlWidth = true;
        vlg.childControlHeight = false;
        vlg.childForceExpandWidth = true;
        vlg.childForceExpandHeight = false;
        vlg.spacing = 4;
        var csf = content.gameObject.AddComponent<ContentSizeFitter>();
        csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;

        sr.viewport = viewport;
        sr.content  = content;

        _scrollContent = content;
    }

    // ── State refresh ────────────────────────────────────────────────────────
    void Refresh(LobbyState state)
    {
        // Lobby code label (preserve art team prefix)
        if (_codeLabel != null)
        {
            const string prefix = "Lobby code: ";
            _codeLabel.text = prefix + (state.JoinCode ?? "------");
        }

        // Max players label - we use this for live player count too. The art
        // team's prefix "Max players: " gets appended with current/max.
        if (_maxPlayersLabel != null)
        {
            int n = state.Players != null ? state.Players.Count : 0;
            _maxPlayersLabel.text = $"Players: {n}";
        }

        // Unified screen for both owner and joiner. Host gets the start
        // button + working settings; non-host gets a clear "waiting" message
        // in the same screen real estate so the layout stays consistent.
        bool isHost = GameManager.Instance?.gameClient?.IsOwner ?? false;
        if (_startBtn != null)
            _startBtn.gameObject.SetActive(isHost);
        if (_waitingForHostText != null)
        {
            _waitingForHostText.gameObject.SetActive(!isHost);
            _waitingForHostText.text = state.Phase == GamePhase.Loading
                ? "⏳  Downloading map data..."
                : "⌛  Waiting for host to start the game...";
        }

        if (_scrollContent == null) return;

        // Clear and rebuild rows. Player count is small (<= 15 per server
        // config), so a full rebuild every refresh is cheap and avoids the
        // bookkeeping of incremental updates.
        foreach (var row in _rows) Destroy(row);
        _rows.Clear();

        if (state.Players == null) return;

        string myId = GameManager.Instance?.gameClient?.ClientUuid ?? "";
        for (int i = 0; i < state.Players.Count; i++)
        {
            var p = state.Players[i];
            bool isMe = p.ClientUuid == myId;
            var row = BuildRow(p.DisplayName ?? "???", isMe, p.IsOwner,
                               i % 2 == 0 ? C_ROW_A : C_ROW_B);
            row.transform.SetParent(_scrollContent, false);
            _rows.Add(row);
        }
    }

    GameObject BuildRow(string playerName, bool isMe, bool isHostPlayer, Color bg)
    {
        const float ROW_H = 130f;
        var go = new GameObject("PlayerRow");
        var rt = go.AddComponent<RectTransform>();
        rt.sizeDelta = new Vector2(0f, ROW_H);
        var le = go.AddComponent<LayoutElement>();
        le.minHeight = ROW_H;
        le.preferredHeight = ROW_H;

        var bgImg = go.AddComponent<Image>();
        bgImg.color = bg;

        // Bottom divider line
        var divRT = MakeRT("Div", rt);
        divRT.anchorMin = new Vector2(0, 0); divRT.anchorMax = new Vector2(1, 0);
        divRT.pivot = new Vector2(0.5f, 0);
        divRT.offsetMin = new Vector2(20, 0); divRT.offsetMax = new Vector2(-20, 2);
        var divImg = divRT.gameObject.AddComponent<Image>();
        divImg.color = C_DIVIDER;

        float nameLeft = 24f;
        if (isHostPlayer)
        {
            var crownRT = MakeRT("Crown", rt);
            crownRT.anchorMin = new Vector2(0, 0.5f); crownRT.anchorMax = new Vector2(0, 0.5f);
            crownRT.pivot     = new Vector2(0, 0.5f);
            crownRT.sizeDelta = new Vector2(90, 90);
            crownRT.anchoredPosition = new Vector2(18, 0);
            var crownTmp = crownRT.gameObject.AddComponent<TextMeshProUGUI>();
            crownTmp.text = "👑"; crownTmp.fontSize = 52;
            crownTmp.color = C_GOLD; crownTmp.alignment = TextAlignmentOptions.Center;
            nameLeft = 118f;
        }

        // Name label
        float nameMaxX = isMe ? 0.68f : 1f;
        var nameRT = MakeRT("Name", rt);
        nameRT.anchorMin = new Vector2(0, 0); nameRT.anchorMax = new Vector2(nameMaxX, 1);
        nameRT.offsetMin = new Vector2(nameLeft, 6); nameRT.offsetMax = new Vector2(-10, -6);
        var nt = nameRT.gameObject.AddComponent<TextMeshProUGUI>();
        nt.text = playerName; nt.fontSize = 48;
        nt.color = isMe ? C_WHITE : C_SOFT;
        nt.alignment = TextAlignmentOptions.MidlineLeft;
        nt.fontStyle = isMe ? FontStyles.Bold : FontStyles.Normal;
        nt.overflowMode = TextOverflowModes.Ellipsis;

        // "YOU" badge
        if (isMe)
        {
            var badgeRT = MakeRT("YouBadge", rt);
            badgeRT.anchorMin = new Vector2(0.68f, 0.22f);
            badgeRT.anchorMax = new Vector2(1f, 0.78f);
            badgeRT.offsetMin = Vector2.zero;
            badgeRT.offsetMax = new Vector2(-20f, 0);
            var badgeImg = badgeRT.gameObject.AddComponent<Image>();
            badgeImg.color = C_ACCENT;
            var badgeTxtRT = MakeRT("Txt", badgeRT);
            Stretch(badgeTxtRT);
            var badgeTmp = badgeTxtRT.gameObject.AddComponent<TextMeshProUGUI>();
            badgeTmp.text = "YOU"; badgeTmp.fontSize = 30;
            badgeTmp.color = C_WHITE; badgeTmp.fontStyle = FontStyles.Bold;
            badgeTmp.alignment = TextAlignmentOptions.Center;
        }

        return go;
    }

    // ── Scene-binding helpers ────────────────────────────────────────────────

    static Button FindButton(Transform root, string name)
    {
        var t = FindByName(root, name);
        return t != null ? t.GetComponent<Button>() : null;
    }

    static TMP_Text FindTMP(Transform root, string name)
    {
        var t = FindByName(root, name);
        if (t == null) return null;
        return t.GetComponent<TMP_Text>() ?? t.GetComponentInChildren<TMP_Text>();
    }

    static Transform FindByName(Transform root, string name)
    {
        if (root == null) return null;
        if (root.name == name) return root;
        foreach (Transform child in root)
        {
            var found = FindByName(child, name);
            if (found != null) return found;
        }
        return null;
    }

    // ── Layout helpers ───────────────────────────────────────────────────────

    static RectTransform MakeRT(string name, Transform parent)
    {
        var go = new GameObject(name);
        var rt = go.AddComponent<RectTransform>();
        rt.SetParent(parent, false);
        rt.localScale = Vector3.one;
        return rt;
    }

    static void Stretch(RectTransform rt)
    {
        rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
        rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
    }

    static void Anchor(RectTransform rt, Vector2 aMin, Vector2 aMax,
        Vector2 offMin, Vector2 offMax)
    {
        rt.anchorMin = aMin; rt.anchorMax = aMax;
        rt.offsetMin = offMin; rt.offsetMax = offMax;
    }
}
