// PlayerDriver.cs — JANKENBOTS Milestone 1, step 2: the replicated player-driven capsule.
//
// WHAT THIS IS (feel over fidelity — throwaway prototype code):
//   A single free-roaming capsule that one player owns and drives around the empty
//   networked scene. It exists purely to prove out the M1 netcode loop before we bolt
//   on the shared bot: "owning client reads input -> host simulates physics -> NGO
//   replicates the transform to everyone." If this capsule slides around smoothly on
//   a friend's screen over Relay, the host-authoritative pipeline works.
//
// HOST-AUTHORITATIVE, NO PREDICTION (decided for this throwaway only):
//   The host owns the Rigidbody and is the ONLY peer that applies force. The owning
//   client never moves itself locally — it just ships its input up to the server every
//   FixedUpdate via an [Rpc(SendTo.Server)]. The server caches that input and, in its
//   own FixedUpdate, pushes the Rigidbody. A server-authoritative NetworkTransform then
//   streams the resulting position/rotation back down to all clients. Because there's
//   no client-side prediction, the local player feels one round-trip of lag on their own
//   capsule — that's fine and expected for M1; we're testing coordination feel, not twitch.
//
// REQUIRED PREFAB SETUP (assumed wired in the Editor — this script does NOT add them):
//   * Rigidbody        — the simulated body. Only the host actually integrates it;
//                        NetworkRigidbody/NetworkTransform force isKinematic=true on
//                        client copies, so DON'T touch isKinematic yourself.
//   * NetworkObject    — makes it a spawnable networked entity; register the prefab in
//                        NetworkManager -> Network Prefabs (or set it as the PlayerPrefab).
//   * NetworkTransform — server-authoritative by default (leave Interpolate = true).
//                        This is what actually replicates our host-driven motion to clients.
//   (A NetworkRigidbody is optional for a slow capsule; add one — with UseRigidBodyForMotion
//    for the fast shove-ball later — if the plain NetworkTransform sync looks jittery.)
//
// Unity 6.x notes baked in below: Rigidbody.linearVelocity (was .velocity); RPC method
// names MUST end in "Rpc"; physics is gated behind IsServer and only ever runs in FixedUpdate.

using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;

namespace Jankenbots.Net
{
    [RequireComponent(typeof(Rigidbody))]
    public class PlayerDriver : NetworkBehaviour
    {
        [Header("Drive feel (host applies these as literal forces)")]
        [Tooltip("Force pushing the capsule along the input direction, in Newtons-ish. Tune for a floaty, cartoony glide — this is a party game, not a sim.")]
        [SerializeField] float moveForce = 40f;

        [Tooltip("Extra damping the host applies when there's no input, so the capsule coasts to a stop instead of drifting forever on the frictionless-ish floor.")]
        [SerializeField] float idleDamping = 4f;

        [Tooltip("Clamp on horizontal speed so a friend can't rocket the capsule off the arena.")]
        [SerializeField] float maxSpeed = 8f;

        // The Rigidbody we drive. Present on every copy, but only the HOST integrates it.
        Rigidbody rb;

        // --- Host-side input cache -------------------------------------------------------
        // The owning client streams its raw stick/keys into these fields via the ServerRpc.
        // The host reads them in FixedUpdate. On client copies these just sit unused.
        // Stored as an already-normalized planar direction (x = strafe, z = forward).
        Vector2 cachedInput;

        void Awake()
        {
            rb = GetComponent<Rigidbody>();
        }

        public override void OnNetworkSpawn()
        {
            // Belt-and-suspenders: physics must only be integrated on the authority (host).
            // NetworkTransform/NetworkRigidbody normally kinematic-lock client copies for us,
            // but if this capsule is used with a bare NetworkTransform we make doubly sure the
            // non-server copies never fight the replicated transform with their own gravity.
            if (!IsServer)
                rb.isKinematic = true;
        }

        // -------------------------------------------------------------------------------------
        // OWNING CLIENT: read input every frame and ship it to the host.
        // We sample in Update (once per rendered frame — cheap, responsive) and send the RPC.
        // The host applies it on its own FixedUpdate clock, so exact send cadence doesn't matter.
        // -------------------------------------------------------------------------------------
        void Update()
        {
            // Only the player who OWNS this capsule may drive it. Everyone else (including the
            // host, for capsules it doesn't own) skips input entirely — IsOwner is the gate.
            if (!IsOwner) return;

            // Throwaway-prototype input via the new Input System. The project ships New-only, so
            // legacy UnityEngine.Input.GetAxis would THROW at runtime — read WASD / arrow keys
            // straight off the current keyboard instead. No action asset needed for an M1 smoke test.
            var kb = Keyboard.current;
            float strafe = 0f, fwd = 0f;
            if (kb != null)
            {
                if (kb.aKey.isPressed || kb.leftArrowKey.isPressed)  strafe -= 1f; // left
                if (kb.dKey.isPressed || kb.rightArrowKey.isPressed) strafe += 1f; // right
                if (kb.wKey.isPressed || kb.upArrowKey.isPressed)    fwd    += 1f; // forward
                if (kb.sKey.isPressed || kb.downArrowKey.isPressed)  fwd    -= 1f; // back
            }

            Vector2 input = new Vector2(strafe, fwd);
            // Normalize so diagonal input isn't ~1.4x faster; keep magnitude for analog sticks.
            if (input.sqrMagnitude > 1f) input.Normalize();

            // Fire the input up to the server. Sending every frame (even zero) is fine for a
            // handful of M1 players — it keeps the host's cache fresh and lets it damp to a stop.
            SubmitMoveInputRpc(input);
        }

        // -------------------------------------------------------------------------------------
        // CLIENT -> HOST input channel. [Rpc(SendTo.Server)] is the NGO 6.x universal RPC form;
        // the method name MUST end in "Rpc". RequireOwnership defaults to true, which is exactly
        // what we want: only this capsule's owner may push input into it.
        // -------------------------------------------------------------------------------------
        [Rpc(SendTo.Server)]
        void SubmitMoveInputRpc(Vector2 input, RpcParams _ = default)
        {
            // Runs on the host. Just cache — never apply force here. All physics happens in the
            // host's FixedUpdate so it's on the fixed timestep the Rigidbody is integrated on.
            cachedInput = input;
        }

        // -------------------------------------------------------------------------------------
        // HOST-ONLY physics. Guarded by IsServer so client copies never integrate — they only
        // receive the replicated NetworkTransform. Everything here runs on the fixed timestep.
        // -------------------------------------------------------------------------------------
        void FixedUpdate()
        {
            if (!IsServer) return;

            // Turn the cached 2D input into a world-space push on the XZ plane. We drive in world
            // axes (not capsule-local) so a capsule with no meaningful facing still moves intuitively
            // relative to the arena — good enough for the M1 "does it replicate?" test.
            Vector3 dir = new Vector3(cachedInput.x, 0f, cachedInput.y);

            if (dir.sqrMagnitude > 0.0001f)
            {
                rb.AddForce(dir * moveForce, ForceMode.Force);
            }
            else
            {
                // No input: bleed off horizontal drift so the capsule settles. Cheap manual damping
                // on the planar velocity only (leave gravity on Y alone).
                Vector3 v = rb.linearVelocity;          // Unity 6: .linearVelocity (was .velocity)
                Vector3 planar = new Vector3(v.x, 0f, v.z);
                planar = Vector3.MoveTowards(planar, Vector3.zero, idleDamping * Time.fixedDeltaTime);
                rb.linearVelocity = new Vector3(planar.x, v.y, planar.z);
            }

            // Clamp planar speed so nobody launches the capsule out of the arena.
            Vector3 vel = rb.linearVelocity;
            Vector3 flat = new Vector3(vel.x, 0f, vel.z);
            if (flat.magnitude > maxSpeed)
            {
                flat = flat.normalized * maxSpeed;
                rb.linearVelocity = new Vector3(flat.x, vel.y, flat.z);
            }
        }
    }
}
