// Assets/_Project/Scripts/UI/MainMenuController.cs
using System.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UIElements;
using TD.Core;
using TD.Gameplay;
using TD.Net;

namespace TD.UI
{
    /// <summary>
    /// Drives the main menu UI. Requires a <see cref="UIDocument"/> on the same
    /// GameObject. Builds the UI programmatically — no UXML needed for v1.
    /// </summary>
    /// <remarks>
    /// <para><b>Buttons.</b>
    /// <list type="bullet">
    ///   <item><b>Host</b> — calls <see cref="NetworkBootstrap.StartHost"/> on
    ///         the default port, then NGO scene-loads the Lobby. Clients that
    ///         join later get pulled into whatever scene the server is in.</item>
    ///   <item><b>Join</b> — reveals IP + port fields, then calls
    ///         <see cref="NetworkBootstrap.StartClient"/>. The server will pull
    ///         the client into the current networked scene (Lobby or Match)
    ///         once the connection completes.</item>
    ///   <item><b>Quit</b> — Application.Quit (no-op in the editor).</item>
    /// </list></para>
    ///
    /// <para><b>Future Steam integration.</b> A lobby-browser panel will be
    /// added here. The Host button will create a Steam lobby instead of a
    /// direct-IP host; Join becomes "browse public lobbies" + "accept friend
    /// invite". See Project_Roadmap.md §1.7-Future Steam Lobby Migration.</para>
    /// </remarks>
    [RequireComponent(typeof(UIDocument))]
    public class MainMenuController : MonoBehaviour
    {
        // ----- Inspector --------------------------------------------------

        [Tooltip("Default port shown in the Join field and used by the Host button.")]
        [SerializeField] private ushort defaultPort = NetworkBootstrap.DefaultPort;

        [Tooltip("Default address pre-filled in the Join field for solo testing.")]
        [SerializeField] private string defaultJoinAddress = NetworkBootstrap.DefaultConnectAddress;

        // ----- Cached UI elements -----------------------------------------

        private Button        hostButton;
        private Button        joinButton;
        private Button        quitButton;
        private Button        quickStartButton;
        private VisualElement hostPanel;
        private TextField     hostPortField;
        private Button        hostConfirmButton;
        private Button        hostCancelButton;
        private VisualElement joinPanel;
        private TextField     joinAddressField;
        private TextField     joinPortField;
        private Button        joinConfirmButton;
        private Button        joinCancelButton;
        private Label         statusLabel;

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

        private void Start()
        {
            // UIDocument creates its panel in OnEnable, which runs after Awake.
            // Start is the safe time to access rootVisualElement.
            var doc = GetComponent<UIDocument>();
            var root = doc?.rootVisualElement;
            if (root == null)
            {
                Debug.LogError("[MainMenuController] rootVisualElement is null. " +
                               "Check the UIDocument's Panel Settings.");
                return;
            }

            BuildUI(root);
        }

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

        private void BuildUI(VisualElement root)
        {
            // Centered vertical stack on a dark background.
            root.style.flexDirection    = FlexDirection.Column;
            root.style.justifyContent   = Justify.Center;
            root.style.alignItems       = Align.Center;
            root.style.backgroundColor  = new Color(0.05f, 0.05f, 0.08f, 1f);
            // The UIDocument root needs explicit width/height to fill the screen.
            root.style.width  = Length.Percent(100);
            root.style.height = Length.Percent(100);

            // Title.
            var title = new Label("Unity Tower Defense");
            title.style.fontSize         = 48;
            title.style.color            = Color.white;
            title.style.unityFontStyleAndWeight = FontStyle.Bold;
            title.style.marginBottom     = 60;
            root.Add(title);

            // Primary button column.
            var buttonColumn = new VisualElement();
            buttonColumn.style.flexDirection = FlexDirection.Column;
            buttonColumn.style.alignItems    = Align.Center;
            root.Add(buttonColumn);

            hostButton       = MakeMenuButton("Host Game",   OnHostClicked);
            joinButton       = MakeMenuButton("Join Game",   OnJoinClicked);
            quitButton       = MakeMenuButton("Quit",        OnQuitClicked);
            quickStartButton = MakeMenuButton("Quick Start", OnQuickStartClicked);
            buttonColumn.Add(hostButton);
            buttonColumn.Add(joinButton);
            buttonColumn.Add(quitButton);
            buttonColumn.Add(quickStartButton);

            // Host sub-panel — hidden until Host is clicked. Holds the port field
            // and the Start Host / Cancel buttons.
            hostPanel = new VisualElement();
            hostPanel.style.flexDirection   = FlexDirection.Column;
            hostPanel.style.alignItems      = Align.Center;
            hostPanel.style.marginTop       = 24;
            hostPanel.style.paddingTop      = 16;
            hostPanel.style.paddingBottom   = 16;
            hostPanel.style.paddingLeft     = 24;
            hostPanel.style.paddingRight    = 24;
            hostPanel.style.backgroundColor = new Color(0f, 0f, 0f, 0.4f);
            hostPanel.style.display         = DisplayStyle.None;
            root.Add(hostPanel);

            hostPortField = new TextField("Port");
            hostPortField.value           = defaultPort.ToString();
            hostPortField.style.width     = 280;
            StyleJoinFieldText(hostPortField);
            hostPanel.Add(hostPortField);

            var hostButtons = new VisualElement();
            hostButtons.style.flexDirection = FlexDirection.Row;
            hostButtons.style.marginTop     = 12;
            hostPanel.Add(hostButtons);

            hostConfirmButton = new Button(OnHostConfirmClicked) { text = "Start Host" };
            hostConfirmButton.style.minWidth    = 120;
            hostConfirmButton.style.height      = 32;
            hostConfirmButton.style.marginRight = 8;
            hostButtons.Add(hostConfirmButton);

            hostCancelButton = new Button(OnHostCancelClicked) { text = "Cancel" };
            hostCancelButton.style.minWidth = 120;
            hostCancelButton.style.height   = 32;
            hostButtons.Add(hostCancelButton);

            // Join sub-panel — hidden until Join is clicked. Holds the IP+port
            // fields and the Connect / Cancel buttons.
            joinPanel = new VisualElement();
            joinPanel.style.flexDirection   = FlexDirection.Column;
            joinPanel.style.alignItems      = Align.Center;
            joinPanel.style.marginTop       = 24;
            joinPanel.style.paddingTop      = 16;
            joinPanel.style.paddingBottom   = 16;
            joinPanel.style.paddingLeft     = 24;
            joinPanel.style.paddingRight    = 24;
            joinPanel.style.backgroundColor = new Color(0f, 0f, 0f, 0.4f);
            joinPanel.style.display         = DisplayStyle.None;
            root.Add(joinPanel);

            joinAddressField = new TextField("Host address");
            joinAddressField.value           = defaultJoinAddress;
            joinAddressField.style.width     = 280;
            StyleJoinFieldText(joinAddressField);
            joinPanel.Add(joinAddressField);

            joinPortField = new TextField("Port");
            joinPortField.value              = defaultPort.ToString();
            joinPortField.style.width        = 280;
            joinPortField.style.marginTop    = 8;
            StyleJoinFieldText(joinPortField);
            joinPanel.Add(joinPortField);

            var joinButtons = new VisualElement();
            joinButtons.style.flexDirection = FlexDirection.Row;
            joinButtons.style.marginTop     = 12;
            joinPanel.Add(joinButtons);

            joinConfirmButton = new Button(OnJoinConfirmClicked) { text = "Connect" };
            joinConfirmButton.style.minWidth     = 120;
            joinConfirmButton.style.height       = 32;
            joinConfirmButton.style.marginRight  = 8;
            joinButtons.Add(joinConfirmButton);

            joinCancelButton = new Button(OnJoinCancelClicked) { text = "Cancel" };
            joinCancelButton.style.minWidth      = 120;
            joinCancelButton.style.height        = 32;
            joinButtons.Add(joinCancelButton);

            // Status line at the bottom for connection feedback.
            statusLabel = new Label(string.Empty);
            statusLabel.style.color       = new Color(1f, 0.6f, 0.3f);
            statusLabel.style.marginTop   = 32;
            statusLabel.style.minHeight   = 20;
            root.Add(statusLabel);
        }

        // TextField's visible text color lives on the inner "unity-text-input"
        // element, not on the TextField root. Setting it on the root alone
        // leaves the inner element's inherited white color in place — which is
        // invisible against the default white input background. Same gotcha as
        // the chat input's dark-styling path in HUDController.
        private static void StyleJoinFieldText(TextField field)
        {
            field.style.color = Color.black;
            var inner = field.Q("unity-text-input");
            if (inner != null)
                inner.style.color = Color.black;
        }

        private static Button MakeMenuButton(string text, System.Action onClick)
        {
            var btn = new Button(() => onClick?.Invoke()) { text = text };
            btn.style.minWidth     = 240;
            btn.style.height       = 48;
            btn.style.fontSize     = 20;
            btn.style.marginBottom = 8;
            return btn;
        }

        // ----- Button handlers --------------------------------------------

        private void OnHostClicked()
        {
            hostPanel.style.display = hostPanel.style.display == DisplayStyle.None
                ? DisplayStyle.Flex
                : DisplayStyle.None;
        }

        private void OnHostConfirmClicked()
        {
            ushort port = defaultPort;
            if (!string.IsNullOrWhiteSpace(hostPortField.value)
                && ushort.TryParse(hostPortField.value.Trim(), out var parsed))
            {
                port = parsed;
            }

            statusLabel.text = $"Starting host on port {port}…";
            if (!NetworkBootstrap.StartHost(port))
            {
                statusLabel.text = "Failed to start host. Check the console.";
                return;
            }

            hostPanel.style.display = DisplayStyle.None;
            NetworkBootstrap.LoadSceneAsHost(SceneNames.Lobby);
        }

        private void OnHostCancelClicked()
        {
            hostPanel.style.display = DisplayStyle.None;
            statusLabel.text = string.Empty;
        }

        private void OnJoinClicked()
        {
            // Toggle the join sub-panel.
            joinPanel.style.display = joinPanel.style.display == DisplayStyle.None
                ? DisplayStyle.Flex
                : DisplayStyle.None;
        }

        private void OnJoinConfirmClicked()
        {
            string address = string.IsNullOrWhiteSpace(joinAddressField.value)
                ? defaultJoinAddress
                : joinAddressField.value.Trim();

            ushort port = defaultPort;
            if (!string.IsNullOrWhiteSpace(joinPortField.value)
                && ushort.TryParse(joinPortField.value.Trim(), out var parsed))
            {
                port = parsed;
            }

            statusLabel.text = $"Connecting to {address}:{port}…";
            if (!NetworkBootstrap.StartClient(address, port))
            {
                statusLabel.text = "Failed to start client. Check the console.";
                return;
            }

            // The server's NGO SceneManager will pull this client into whatever
            // scene the server is in (Lobby or Match) once the connection
            // completes. SessionFlow handles disconnect recovery.
            joinPanel.style.display = DisplayStyle.None;
        }

        private void OnJoinCancelClicked()
        {
            joinPanel.style.display = DisplayStyle.None;
            statusLabel.text = string.Empty;
        }

        private void OnQuitClicked()
        {
#if UNITY_EDITOR
            UnityEditor.EditorApplication.isPlaying = false;
#else
            Application.Quit();
#endif
        }

        // Dev / testing shortcut: skips the lobby entirely. Hosts a single-player
        // session, auto-selects Race1 for the local player, and loads the Match
        // scene directly. Useful for iterating on gameplay without clicking
        // through Host → Lobby → Pick Race → Ready → Start every time.
        //
        // To remove for a shipping build: delete the button-creation lines in
        // BuildUI plus this method and its coroutine. No other consumers.
        private void OnQuickStartClicked()
        {
            statusLabel.text = "Quick starting…";
            StartCoroutine(QuickStartCoroutine());
        }

        private IEnumerator QuickStartCoroutine()
        {
            if (!NetworkBootstrap.StartHost(defaultPort))
            {
                statusLabel.text = "Failed to start host. Check the console.";
                yield break;
            }

            // Wait for the local player's PlayerMatchState to spawn. Empirically
            // this happens synchronously inside StartHost, but waiting one frame
            // is cheap insurance against future NGO changes to spawn timing.
            // Cap the wait at 60 frames so a real failure doesn't silently hang.
            int safetyFrames = 0;
            while (PlayerMatchState.Local == null && safetyFrames++ < 60)
                yield return null;

            var pms = PlayerMatchState.Local;
            if (pms == null)
            {
                Debug.LogError("[MainMenuController] Quick Start: PlayerMatchState.Local " +
                               "didn't appear within 60 frames after StartHost. Aborting.");
                statusLabel.text = "Quick Start failed: player did not spawn.";
                yield break;
            }

            // Server-only setters — host is server + client, so these are valid
            // directly. Skipping the RPC round-trip avoids any frame-of-latency
            // before LoadSceneAsHost reads RaceSelection on the way into Match.
            pms.SetRaceSelection(RaceId.Race1);
            pms.SetReady(true);

            // Skip Lobby — drop straight into the default map's scene. Pulls from
            // MapRegistry.Default (the first map authored in the registry — by convention
            // the 9-player map). Falls back to the hardcoded SceneNames.Match if the
            // registry isn't present (e.g. testing in editor without going through MainMenu,
            // though that's the very scene this controller lives in, so it should always
            // resolve in practice).
            string sceneToLoad;
            var registry = MapRegistry.Instance;
            var defaultMap = registry != null ? registry.Default : null;
            if (defaultMap != null && !string.IsNullOrEmpty(defaultMap.SceneName))
            {
                sceneToLoad = defaultMap.SceneName;
                Debug.Log($"[MainMenu] Quick Start loading default map '{defaultMap.MapName}' " +
                          $"(scene='{sceneToLoad}').");
            }
            else
            {
                sceneToLoad = SceneNames.Match;
                Debug.LogWarning($"[MainMenu] Quick Start: MapRegistry default unavailable, " +
                                 $"falling back to hardcoded scene '{sceneToLoad}'.");
            }

            NetworkBootstrap.LoadSceneAsHost(sceneToLoad);
        }
    }
}
