// Assets/_Project/Scripts/Gameplay/BuilderInputController.cs
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using TD.Core;
using TD.UI;

namespace TD.Gameplay
{
    /// <summary>
    /// Owner-only client-side controller for builder input. Handles selection,
    /// right-click-to-move (with side-effects: pause active construction and
    /// refund tail jobs), right-click-on-paused-build-site (resume), and
    /// Escape-to-deselect.
    /// </summary>
    /// <remarks>
    /// <para><b>Owner-only.</b> This component lives on the same GameObject as
    /// <see cref="Builder"/> but only its owning client processes input. Non-owner
    /// clients have this component but its Update is a no-op.</para>
    ///
    /// <para><b>Selection model.</b> Left-click raycast against the
    /// <see cref="selectionLayerMask"/> (the builder's selection trigger collider
    /// sits on this layer). Hitting the local builder selects it; hitting empty
    /// space or another collider clears the selection. Selection lives in the
    /// singleton <see cref="SelectionState"/>.</para>
    ///
    /// <para><b>Right-click — selection required.</b> Without selection, right-click
    /// is a no-op. With selection:
    /// <list type="bullet">
    ///   <item>Right-click on the local builder's PAUSED build site (raycast hits
    ///         <see cref="buildSiteLayerMask"/>): resume that job. Builder walks back
    ///         into range; preserved progress picks up where it left off.</item>
    ///   <item>Right-click anywhere else on the buildable plane: move-and-pause.
    ///         Builder walks to the clicked location. Side effects:
    ///         <list type="bullet">
    ///           <item>If currently Constructing → pause the active build, refund
    ///                 tail jobs.</item>
    ///           <item>If head is Queued → refund the entire queue.</item>
    ///           <item>If head is Paused → no queue change (tail was already
    ///                 refunded at pause time).</item>
    ///         </list>
    ///   </item>
    /// </list></para>
    ///
    /// <para><b>Escape.</b> Clears selection. No queue cancellation in D2 — that's
    /// deferred to the HUD path.</para>
    ///
    /// <para><b>Placement mode interaction.</b> If the local TowerPlacementController
    /// reports IsPlacing == true, all input handled here yields to it (placement-mode
    /// right-click cancels placement, placement-mode left-click submits). Selection
    /// raycasts are also suppressed so a placement click doesn't accidentally
    /// deselect the builder.</para>
    /// </remarks>
    public class BuilderInputController : NetworkBehaviour
    {
        // ----- Inspector --------------------------------------------------

        [Tooltip("Physics layer mask for the BuildablePlane collider. Cursor is raycast " +
                 "against this layer to determine the move target.")]
        [SerializeField] private LayerMask buildablePlaneLayerMask;

        [Tooltip("Physics layer mask for selection trigger colliders. Builder selection " +
                 "colliders AND tower selection colliders both sit on this layer. The " +
                 "raycast walks up the hit hierarchy to find an ISelectable component, so " +
                 "any selectable kind on this layer Just Works. The mask must NOT overlap " +
                 "with BuildablePlane or TerrainGeometry; selection is a separate concern.")]
        [SerializeField] private LayerMask selectionLayerMask;

        [Tooltip("Physics layer mask for build-site visual click targets. The " +
                 "BuildSiteVisual prefab carries a small trigger collider on this layer " +
                 "so right-click can identify a paused build site for resume. Must NOT " +
                 "overlap with BuildablePlane, Selection, or TerrainGeometry.")]
        [SerializeField] private LayerMask buildSiteLayerMask;

        [Tooltip("Maximum raycast distance for cursor → world conversion.")]
        [SerializeField] private float raycastMaxDistance = 500f;

        // Cached reference to the sibling Builder component.
        private Builder builder;

        // Cached reference to the local TowerPlacementController, looked up lazily because
        // it may not exist at OnNetworkSpawn time.
        private TowerPlacementController cachedPlacementController;

        // Cached reference to the local TowerPaintController, looked up lazily (same
        // rationale as the placement controller).
        private TowerPaintController cachedPaintController;

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

        public override void OnNetworkSpawn()
        {
            builder = GetComponent<Builder>();
            if (builder == null)
            {
                Debug.LogError("[BuilderInputController] Missing Builder component on the " +
                               "same GameObject. Disabling input.");
                enabled = false;
                return;
            }

            // Non-owners do nothing.
            if (!IsOwner)
            {
                enabled = false;
            }
        }

        // ----- Input handling ---------------------------------------------

        private void Update()
        {
            // Defensive: enabled is set false for non-owners in OnNetworkSpawn, but keep
            // the IsOwner check here in case order-of-operations changes.
            if (!IsOwner) return;

            var mouse = Mouse.current;
            var keyboard = Keyboard.current;
            if (mouse == null) return;

            // Placement and paint are both modal: while either is active, the local
            // controller for that mode owns left-/right-click, so selection here yields.
            bool isModal = IsLocalPlayerPlacing() || IsLocalPlayerPainting();
            Vector2 mousePos = mouse.position.ReadValue();

            // UI Toolkit dispatches button click events AFTER Update runs, but raw mouse
            // input is already true this frame. Without this gate, clicking a HUD button
            // also fires HandleLeftClickSelection — the raycast misses the builder collider
            // and deselects before the button's action fires. Same risk on right-click.
            bool pointerOverHud = HUDController.IsPointerOverInteractiveHud(mousePos);

            // Left-click: selection. Suppressed during a modal mode (placement-submit /
            // paint-apply own the click there) and when the pointer is over HUD.
            if (!isModal && !pointerOverHud && mouse.leftButton.wasPressedThisFrame)
            {
                HandleLeftClickSelection(mousePos);
            }

            // Escape: clear selection. Allowed during placement mode too — Escape never
            // means anything else here, and clearing selection during placement is fine.
            // Suppressed while chat (or any HUD text field) has focus, since Escape there
            // means "cancel typing" and should not also clear the unit selection.
            if (keyboard != null && keyboard.escapeKey.wasPressedThisFrame
                && !HUDController.IsTextInputActive)
            {
                SelectionState.Instance?.Clear();
            }

            // Right-click. Suppressed entirely during a modal mode (placement/paint
            // controllers handle right-click as cancel there) and when over HUD.
            if (isModal) return;

            // B: toggle buff menu.
            if (keyboard != null && keyboard.bKey.wasPressedThisFrame
                && !HUDController.IsTextInputActive)
            {
                HUDController.Instance?.ToggleBuffMenu();
            }
            
            if (pointerOverHud) return;
            if (!mouse.rightButton.wasPressedThisFrame) return;

            HandleRightClick(mousePos);
        }

        // ----- Selection (left-click) -------------------------------------

        private void HandleLeftClickSelection(Vector2 screenPos)
        {
            var selection = SelectionState.Instance;
            if (selection == null) return;

            var cam = Camera.main;
            if (cam == null) return;

            Ray ray = cam.ScreenPointToRay(new Vector3(screenPos.x, screenPos.y, 0f));

            // Single raycast against the unified Selection layer. Whatever we hit, walk
            // up its hierarchy to find an ISelectable component (Builder, Tower, or
            // any future kind). The closest hit wins automatically — no priority logic
            // needed because builders and towers don't visually overlap in practice.
            if (Physics.Raycast(ray, out RaycastHit hit, raycastMaxDistance, selectionLayerMask))
            {
                var hitSelectable = hit.collider.GetComponentInParent<ISelectable>();
                if (hitSelectable != null)
                {
                    // Don't let players select someone else's builder. Treat that as
                    // "clicked empty" so we clear, rather than steal their selection.
                    if (hitSelectable is Builder b && b != builder)
                    {
                        selection.Clear();
                        return;
                    }
                    selection.Select(hitSelectable);
                    return;
                }
            }

            // Clicked empty space or a non-selectable thing — clear selection.
            selection.Clear();
        }

        // ----- Right-click dispatch ---------------------------------------

        private void HandleRightClick(Vector2 screenPos)
        {
            // Right-click is gated by selection: nothing happens unless the local
            // builder is the selected one.
            var selection = SelectionState.Instance;
            if (selection == null || !selection.IsSelected(builder)) return;

            var cam = Camera.main;
            if (cam == null) return;
            Ray ray = cam.ScreenPointToRay(new Vector3(screenPos.x, screenPos.y, 0f));

            // Test 1: did we hit a build-site visual? If so, and it belongs to OUR
            // builder, AND it's currently shelved, this is a resume command.
            // The BuildSite raycast is checked first because the build-site visual
            // sits ABOVE the buildable plane visually; a click on the cube should
            // be interpreted as a build-site click, not a "move to under the cube" click.
            if (Physics.Raycast(ray, out RaycastHit buildSiteHit, raycastMaxDistance,
                                buildSiteLayerMask))
            {
                var visual = buildSiteHit.collider.GetComponentInParent<BuildSiteVisual>();
                if (visual != null
                    && visual.IsShelved
                    && visual.OwnerClientId == NetworkManager.Singleton.LocalClientId)
                {
                    // Resume command. Send the visual's NetworkObjectReference so the
                    // server can identify which shelved tower the player clicked.
                    // The server handles pre-empting any in-progress queue work.
                    var visualNetObj = visual.GetComponent<NetworkObject>();
                    if (visualNetObj != null)
                    {
                        builder.RequestResumeShelvedRpc(new NetworkObjectReference(visualNetObj));
                        return;
                    }
                }
                // Hit a build-site visual that wasn't shelved-and-ours. Fall through
                // to the move case so the click still does something useful (move
                // the builder to that approximate world position).
            }

            // Test 2: hit the buildable plane? Standard move-and-pause command.
            if (Physics.Raycast(ray, out RaycastHit planeHit, raycastMaxDistance,
                                buildablePlaneLayerMask))
            {
                builder.RequestMoveAndPauseRpc(planeHit.point);
                return;
            }

            // Hit nothing relevant — no-op.
        }

        // ----- Helpers ----------------------------------------------------

        private bool IsLocalPlayerPlacing()
        {
            if (cachedPlacementController == null)
            {
                // Find lazily — controller may have been added after this component spawned.
                cachedPlacementController =
                    UnityEngine.Object.FindAnyObjectByType<TowerPlacementController>();
                if (cachedPlacementController == null) return false;
            }
            return cachedPlacementController.IsPlacing;
        }

        private bool IsLocalPlayerPainting()
        {
            if (cachedPaintController == null)
            {
                // Find lazily — controller may have been added after this component spawned.
                cachedPaintController =
                    UnityEngine.Object.FindAnyObjectByType<TowerPaintController>();
                if (cachedPaintController == null) return false;
            }
            return cachedPaintController.IsPainting;
        }
    }
}