using Unity.Cinemachine;
using UnityEngine;
using UnityEngine.InputSystem;

namespace Jankenbots.Prototype
{
    /// <summary>
    /// JANKENBOTS — client-local camera director. Owns four Cinemachine cameras, lets
    /// the local player cycle between them and orbit/zoom the follow cams; the
    /// CinemachineBrain on this Camera blends smoothly on every switch.
    ///
    ///   • AvatarFollow  — orbitable 3rd-person cam on YOUR character (on-foot).
    ///   • RobotFollow   — orbitable chase cam on the bot (driving).
    ///   • StandView     — fixed view from your player stand, looking at the bot.
    ///   • CinematicOrbit— slow auto-orbit of the bot (spectate / flavor).
    ///
    /// LOOK / ZOOM (Avatar & Robot only): hold RIGHT MOUSE and drag to orbit
    /// (yaw/pitch), scroll wheel to zoom in/out (out to <see cref="maxDistance"/> so
    /// you can pull back and see the arena). RMB-drag keeps the cursor free for the
    /// Claim/Leave buttons until the walk-up interaction exists.
    ///
    /// Default mode auto-follows the control state (on foot → Avatar, seated → Robot),
    /// reading the same seat flag as the control handoff (<see cref="PlayerAvatar.IsSeatedLocal"/>).
    /// Press V to cycle manually. Cameras are purely client-local, never networked.
    /// </summary>
    [RequireComponent(typeof(Camera))]
    public class CameraDirector : MonoBehaviour
    {
        public enum Mode { AvatarFollow, RobotFollow, StandView, CinematicOrbit }

        [Header("Follow distances")]
        public float avatarDistance = 8f;
        public float robotDistance  = 16f;

        [Header("Look / zoom (Avatar & Robot)")]
        [Tooltip("Degrees of orbit per pixel of mouse drag (while holding RMB).")]
        public float lookSensitivity = 0.18f;
        [Tooltip("Metres of zoom per scroll notch.")]
        public float zoomSensitivity = 4f;
        public float minDistance = 3f;
        [Tooltip("How far you can pull the camera back. Big so you can survey the arena.")]
        public float maxDistance = 80f;
        public float minPitch = -8f;
        public float maxPitch = 78f;
        public float lookHeight = 1.2f;   // aim a bit above the target pivot

        [Header("Cinematic orbit")]
        public float orbitRadius = 22f;
        public float orbitHeight = 10f;
        public float orbitSpeed  = 16f;   // deg/sec

        Transform _bot, _standOverlook;
        CinemachineCamera _avatar, _robot, _stand, _orbit;
        CinemachineCamera[] _all;
        Mode _mode = Mode.AvatarFollow;
        bool _userOverride;
        bool _lastSeated;

        // Orbit state shared by the two follow cams (player-controlled).
        float _yaw;
        float _pitch = 18f;
        float _dist = 8f;
        float _cineAngle;
        bool _cursorFree;   // Esc toggles the locked cursor free (for GUI / to escape play)

        PlayerAvatar _local;

        void Start()
        {
            if (GetComponent<CinemachineBrain>() == null) gameObject.AddComponent<CinemachineBrain>();
            var legacy = GetComponent<FollowCamera>();
            if (legacy != null) legacy.enabled = false;

            var b = GameObject.Find("Bot");          if (b != null) _bot = b.transform;
            var s = GameObject.Find("StandPoint_0");  if (s != null) _standOverlook = s.transform;

            var holder = new GameObject("CameraRigs").transform;
            _avatar = Make(holder, "CM_AvatarFollow");
            _robot  = Make(holder, "CM_RobotFollow");
            _stand  = Make(holder, "CM_StandView");
            _orbit  = Make(holder, "CM_CinematicOrbit");
            _all = new[] { _avatar, _robot, _stand, _orbit };
            Apply(_mode);
        }

        CinemachineCamera Make(Transform parent, string name)
        {
            var go = new GameObject(name);
            go.transform.SetParent(parent, false);
            var vc = go.AddComponent<CinemachineCamera>();
            vc.Priority = 0;
            return vc;
        }

        void Update()
        {
            if (_local == null) _local = FindLocal();

            var kb = Keyboard.current;
            if (kb != null && kb.vKey.wasPressedThisFrame)
            {
                _mode = (Mode)(((int)_mode + 1) % _all.Length);
                _userOverride = true;
                Apply(_mode);
            }

            // Seat claim/leave re-asserts the sensible default (clears a manual override).
            if (_local != null && _local.IsSeatedLocal != _lastSeated)
            {
                _lastSeated = _local.IsSeatedLocal;
                _userOverride = false;
            }
            if (!_userOverride && _local != null)
            {
                Mode want = _local.IsSeatedLocal ? Mode.RobotFollow : Mode.AvatarFollow;
                if (want != _mode) { _mode = want; Apply(_mode); }
            }

            // Cursor + look. On-foot (AvatarFollow) uses ALWAYS-ON mouse-look with a
            // locked cursor (no RMB needed); other modes keep the cursor free (RobotFollow
            // orbits while RMB is held). Esc toggles the cursor free to reach GUI / escape.
            if (kb != null && kb.escapeKey.wasPressedThisFrame) _cursorFree = !_cursorFree;
            bool avatarLook = _mode == Mode.AvatarFollow && !_cursorFree;
            Cursor.lockState = avatarLook ? CursorLockMode.Locked : CursorLockMode.None;
            Cursor.visible   = !avatarLook;

            if (_mode == Mode.AvatarFollow || _mode == Mode.RobotFollow)
            {
                var m = Mouse.current;
                if (m != null)
                {
                    bool look = _mode == Mode.AvatarFollow ? avatarLook : m.rightButton.isPressed;
                    if (look)
                    {
                        Vector2 d = m.delta.ReadValue();
                        _yaw += d.x * lookSensitivity;
                        _pitch = Mathf.Clamp(_pitch - d.y * lookSensitivity, minPitch, maxPitch);
                    }
                    float scroll = m.scroll.ReadValue().y;
                    if (Mathf.Abs(scroll) > 0.01f)
                        _dist = Mathf.Clamp(_dist - Mathf.Sign(scroll) * zoomSensitivity, minDistance, maxDistance);
                }
            }
        }

        void LateUpdate()
        {
            if (_local != null) PoseOrbit(_avatar, _local.transform);

            if (_bot != null)
            {
                PoseOrbit(_robot, _bot);

                _cineAngle += orbitSpeed * Time.deltaTime;
                Vector3 focus = _bot.position + Vector3.up * lookHeight;
                Vector3 cineOff = Quaternion.Euler(0f, _cineAngle, 0f) * new Vector3(0f, orbitHeight, -orbitRadius);
                _orbit.transform.position = focus + cineOff;
                _orbit.transform.rotation = Quaternion.LookRotation(focus - _orbit.transform.position);

                if (_standOverlook != null)
                {
                    _stand.transform.position = _standOverlook.position + Vector3.up * 1.5f;
                    _stand.transform.rotation = Quaternion.LookRotation(
                        (_bot.position + Vector3.up * lookHeight) - _stand.transform.position);
                }
            }
        }

        // Player-controllable orbit: spherical offset from the target driven by _yaw/_pitch/_dist.
        void PoseOrbit(CinemachineCamera vc, Transform target)
        {
            Vector3 focus = target.position + Vector3.up * lookHeight;
            Vector3 dir = Quaternion.Euler(_pitch, _yaw, 0f) * Vector3.back;
            Vector3 pos = focus + dir * _dist;
            vc.transform.position = pos;
            vc.transform.rotation = Quaternion.LookRotation(focus - pos);
        }

        PlayerAvatar FindLocal()
        {
            foreach (var a in FindObjectsByType<PlayerAvatar>(FindObjectsSortMode.None))
                if (a.IsOwner) return a;
            return null;
        }

        void Apply(Mode m)
        {
            for (int i = 0; i < _all.Length; i++)
                _all[i].Priority = ((int)m == i) ? 20 : 0;

            // Reset zoom to the mode's default distance on switch (keep the look angle).
            if (m == Mode.AvatarFollow) _dist = avatarDistance;
            else if (m == Mode.RobotFollow) _dist = robotDistance;
        }
    }
}
