// Assets/_Project/Scripts/Gameplay/TowerPaintController.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using TD.Core;
using TD.UI;

namespace TD.Gameplay
{
    /// <summary>
    /// Per-client controller for the tower Paint UX. Mirrors
    /// <see cref="TowerPlacementController"/>: a plain (non-networked) MonoBehaviour that
    /// owns purely local, cosmetic state — the active brush color and the hardware cursor.
    /// The authoritative recolor lives on <see cref="TowerInstance"/> via
    /// <see cref="TowerInstance.RequestPaintServerRpc"/>.
    /// </summary>
    /// <remarks>
    /// <para><b>Paint mode.</b> <see cref="BeginPaint"/> activates a brush (Red/Green/Blue
    /// or the <see cref="PaintColor.None"/> Reset brush) and swaps the OS cursor to a
    /// tinted circle. While active, a left-click that hits a <see cref="TowerInstance"/>
    /// the local player owns sends a paint request; the player stays in paint mode so
    /// several towers can be painted in a row. Right-click or Escape exits.</para>
    ///
    /// <para><b>Active vs. color.</b> <see cref="IsPainting"/> tracks whether a brush is
    /// active independently of <see cref="CurrentColor"/>, because <c>None</c> is a valid
    /// active brush (Reset) — not the "off" state.</para>
    ///
    /// <para><b>Interaction with selection.</b> <see cref="BuilderInputController"/> checks
    /// <see cref="IsPainting"/> and suppresses its own left-/right-click handling while a
    /// brush is active, so painting never also moves or deselects the builder.</para>
    /// </remarks>
    public class TowerPaintController : MonoBehaviour
    {
        // ----- Inspector --------------------------------------------------

        [Tooltip("Maximum raycast distance from the camera into the world. Should be at " +
                 "least as large as the camera's far clip plane.")]
        [SerializeField] private float raycastMaxDistance = 500f;

        [Tooltip("Diameter in pixels of the generated paint cursor. Larger than a dot so " +
                 "it reads clearly as a paint brush.")]
        [SerializeField] private int cursorSize = 28;

        // ----- State ------------------------------------------------------

        private PaintColor currentColor = PaintColor.None;

        /// <summary>True while a paint brush is active. Independent of the brush color
        /// (the Reset / <see cref="PaintColor.None"/> brush is still an active brush).</summary>
        public bool IsPainting { get; private set; }

        /// <summary>The active brush color. Only meaningful while <see cref="IsPainting"/>.</summary>
        public PaintColor CurrentColor => currentColor;

        // Generated cursor textures, cached per color so we don't rebuild every activation.
        private readonly Dictionary<PaintColor, Texture2D> cursorTextures =
            new Dictionary<PaintColor, Texture2D>();

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

        private void OnDisable()
        {
            // Restore the OS cursor if this is torn down mid-paint.
            CancelPaint();
        }

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

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

            // Right-click or Escape exits paint mode. (Escape is ignored while a HUD text
            // field has focus so it means "cancel typing" there, matching other systems.)
            bool escape = keyboard != null
                          && keyboard.escapeKey.wasPressedThisFrame
                          && !HUDController.IsTextInputActive;
            if (mouse.rightButton.wasPressedThisFrame || escape)
            {
                CancelPaint();
                return;
            }

            // Left-click paints the tower under the cursor (if the local player owns it).
            if (!mouse.leftButton.wasPressedThisFrame) return;

            Vector2 mousePos = mouse.position.ReadValue();
            if (HUDController.IsPointerOverInteractiveHud(mousePos)) return;   // clicked the HUD

            TryPaintAtCursor(mousePos);
        }

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

        /// <summary>
        /// Activates the given paint brush and swaps the cursor. Calling this with the
        /// already-active brush toggles paint mode off (so a second press of the same
        /// hotkey cancels). Call from HUD paint swatches.
        /// </summary>
        public void BeginPaint(PaintColor color)
        {
            // Toggle off if the same brush is re-selected.
            if (IsPainting && color == currentColor)
            {
                CancelPaint();
                return;
            }

            currentColor = color;
            IsPainting = true;

            var tex = GetCursorTexture(color);
            Vector2 hotspot = new Vector2(tex.width * 0.5f, tex.height * 0.5f);
            Cursor.SetCursor(tex, hotspot, CursorMode.Auto);
        }

        /// <summary>
        /// Exits paint mode and restores the default OS cursor. Safe to call when idle.
        /// </summary>
        public void CancelPaint()
        {
            if (!IsPainting) return;
            IsPainting = false;
            Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
        }

        // ----- Painting ---------------------------------------------------

        private void TryPaintAtCursor(Vector2 screenPos)
        {
            var cam = Camera.main;
            if (cam == null) return;

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

            // A tower is identified by its TowerInstance component, not by a physics
            // layer — its selection/body colliders may live on different layers. Raycast
            // all layers (including triggers, since selection volumes are triggers) and
            // keep the nearest hit that resolves to a TowerInstance; non-tower hits
            // (terrain, buildable plane, the builder) are simply ignored.
            var hits = Physics.RaycastAll(ray, raycastMaxDistance, ~0,
                                          QueryTriggerInteraction.Collide);

            TowerInstance tower = null;
            float nearest = float.MaxValue;
            foreach (var h in hits)
            {
                var t = h.collider.GetComponentInParent<TowerInstance>();
                if (t != null && h.distance < nearest)
                {
                    nearest = h.distance;
                    tower = t;
                }
            }

            if (tower == null) return;   // clicked empty space / non-tower — no-op

            // Own-towers-only: skip non-owned towers locally (the server re-validates too).
            if (tower.Owner != GetLocalPlayerSlot()) return;

            tower.RequestPaintServerRpc(currentColor);
        }

        private static PlayerSlot GetLocalPlayerSlot()
            => PlayerMatchState.Local?.Slot ?? PlayerSlot.None;

        // ----- Cursor texture ---------------------------------------------

        // Builds (and caches) a filled-circle cursor texture tinted to the paint color,
        // with a darker rim so it reads against any background. The None/Reset brush is a
        // neutral gray circle. Pixels outside the circle are transparent.
        private Texture2D GetCursorTexture(PaintColor color)
        {
            if (cursorTextures.TryGetValue(color, out var cached) && cached != null)
                return cached;

            int size = Mathf.Max(8, cursorSize);
            var tex = new Texture2D(size, size, TextureFormat.RGBA32, false);

            Color fill = PaintColors.Get(color);
            Color rim = fill * 0.45f;
            rim.a = 1f;

            float center = (size - 1) * 0.5f;
            float outer = center;             // outer edge of the disc
            float rimStart = outer - 2f;      // 2px rim

            var pixels = new Color[size * size];
            for (int y = 0; y < size; y++)
            {
                for (int x = 0; x < size; x++)
                {
                    float dx = x - center;
                    float dy = y - center;
                    float dist = Mathf.Sqrt(dx * dx + dy * dy);

                    Color c;
                    if (dist > outer) c = new Color(0f, 0f, 0f, 0f);   // transparent outside
                    else if (dist >= rimStart) c = rim;                // dark rim
                    else c = fill;                                     // solid center

                    pixels[y * size + x] = c;
                }
            }

            tex.SetPixels(pixels);
            tex.Apply();
            tex.filterMode = FilterMode.Bilinear;

            cursorTextures[color] = tex;
            return tex;
        }
    }
}
