using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using Unity.Cinemachine;

/// <summary>
/// GAME 1303 Tier 8 — Apep's Trial's core mechanic: the camera switches from first-person
/// 3D to a 2D side-scroller or top-down view, and the controls change to match.
///
/// HOW IT WORKS
///  The player object carries BOTH control schemes at once (FirstPersonController for 3D,
///  PlayerController2D for side-scrolling, TopDownMover for top-down) plus one Cinemachine
///  camera per mode. Switching = enable one controller, disable the others, raise the
///  priority of that mode's camera, and let Cinemachine blend between them.
///
/// SETUP
///  1. Player: CharacterController + FirstPersonController, AND Rigidbody2D +
///     PlayerController2D, AND TopDownMover. Disable two of the three in the Inspector.
///     (Yes, both a CharacterController and a Rigidbody2D on one object — they operate in
///     different axes and don't fight as long as only one controller is enabled.)
///  2. Three Cinemachine cameras in the scene, one per mode. Drag each into its slot.
///  3. Set the CinemachineBrain's Default Blend to Ease In Out, ~0.6s.
///  4. Switch modes from a trigger: put a ModeSwitchTrigger (below) at the doorway where
///     the perspective changes, or call SetMode() from any UnityEvent.
///  5. Lock Input During Blend is on by default — letting the player move mid-blend is
///     how you get people walking off ledges they couldn't see.
///
/// A WORD OF ADVICE FOR LAZARO: prototype this switch on a flat grey box level FIRST,
/// before any art or level design. The mechanic either feels good or it doesn't, and you
/// want to know which in week two rather than week ten.
/// </summary>
public class CameraModeSwitcher : MonoBehaviour
{
    public enum Mode { FirstPerson3D, SideScroll2D, TopDown2D }

    [Header("Cameras")]
    public CinemachineCamera camera3D;
    public CinemachineCamera cameraSide;
    public CinemachineCamera cameraTopDown;
    public int activePriority = 20;
    public int inactivePriority = 5;

    [Header("Controllers (on the player)")]
    public FirstPersonController controller3D;
    public PlayerController2D controllerSide;
    public TopDownMover controllerTopDown;
    public CharacterController characterController;
    public Rigidbody2D body2D;

    [Header("Switching")]
    public Mode startingMode = Mode.FirstPerson3D;
    public float blendTime = 0.6f;
    public bool lockInputDuringBlend = true;
    [Tooltip("Freeze the 2D rigidbody while in 3D mode (and vice versa).")]
    public bool manageBodies = true;

    [Header("Feedback")]
    public string switchSfx = "mode_switch";
    public UnityEvent<Mode> OnModeChanged;
    public UnityEvent OnSwitchStarted;
    public UnityEvent OnSwitchFinished;

    public Mode CurrentMode { get; private set; }
    public bool IsSwitching { get; private set; }

    void Start() => ApplyMode(startingMode, true);

    /// <summary>Wire a trigger or UnityEvent here. 0 = 3D, 1 = side, 2 = top-down.</summary>
    public void SetModeByIndex(int index) => SetMode((Mode)Mathf.Clamp(index, 0, 2));

    public void SetMode(Mode mode)
    {
        if (mode == CurrentMode || IsSwitching) return;
        StartCoroutine(SwitchRoutine(mode));
    }

    IEnumerator SwitchRoutine(Mode mode)
    {
        IsSwitching = true;
        OnSwitchStarted?.Invoke();
        if (!string.IsNullOrEmpty(switchSfx)) AudioManager.Instance?.PlaySFX(switchSfx);

        if (lockInputDuringBlend) SetAllInput(false);

        ApplyMode(mode, false);

        yield return new WaitForSeconds(blendTime);

        if (lockInputDuringBlend) SetAllInput(true);
        IsSwitching = false;
        OnSwitchFinished?.Invoke();
    }

    void ApplyMode(Mode mode, bool instant)
    {
        CurrentMode = mode;

        // Cameras: priority decides which one Cinemachine blends to
        if (camera3D != null)      camera3D.Priority      = mode == Mode.FirstPerson3D ? activePriority : inactivePriority;
        if (cameraSide != null)    cameraSide.Priority    = mode == Mode.SideScroll2D  ? activePriority : inactivePriority;
        if (cameraTopDown != null) cameraTopDown.Priority = mode == Mode.TopDown2D     ? activePriority : inactivePriority;

        // Controllers: exactly one is enabled
        bool is3D = mode == Mode.FirstPerson3D;
        bool isSide = mode == Mode.SideScroll2D;
        bool isTop = mode == Mode.TopDown2D;

        if (controller3D != null)      controller3D.enabled = is3D;
        if (controllerSide != null)    controllerSide.enabled = isSide;
        if (controllerTopDown != null) controllerTopDown.enabled = isTop;

        if (manageBodies)
        {
            if (characterController != null) characterController.enabled = is3D;
            if (body2D != null)
            {
                body2D.simulated = !is3D;
                if (!is3D) body2D.gravityScale = isSide ? 3f : 0f;
                body2D.linearVelocity = Vector2.zero;
            }
        }

        // Cursor: locked in first person, free otherwise
        if (controller3D != null)
        {
            if (is3D) controller3D.LockCursor();
            else controller3D.UnlockCursor();
        }

        OnModeChanged?.Invoke(mode);
    }

    void SetAllInput(bool enabled)
    {
        if (controller3D != null)      controller3D.InputEnabled = enabled;
        if (controllerSide != null)    controllerSide.InputEnabled = enabled;
        if (controllerTopDown != null) controllerTopDown.InputEnabled = enabled;
    }
}

/*  ---- Cinemachine 2.x (Unity 2022) ----
    using Cinemachine;  and  CinemachineVirtualCamera instead of CinemachineCamera.
    Priority is an int on both.
*/