using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
    // =================================================
    // VARIABLES EDITABLES EN EL INSPECTOR
    // =================================================
    
    [Header("General Config")]
    public float moveSpeed = 5f;           // Velocidad base de movimiento
    public float jumpForce = 5f;           // Fuerza del salto normal
    public float risingGravity = -15f;     // Fuerza de gravedad al saltar 
    public float fallingGravity = -25f;    // Fuerza de gravedad al caer
    public Transform cameraTransform;      // Transform de la cámara principal
    public float groundCheckRadius = 0.4f; // Radio para detección de suelo
    
    [Header("Components")]
    public Transform groundCheck;          // Objeto para detectar suelo
    public LayerMask groundMask;           // Capas consideradas como suelo
    [Header("Abilities")]
    public bool canDoubleJump = false;     // Doble salto
    public bool canDash = false;           // Dash o movimiento rápido
    public bool canWallJump = false;       // Salto en pared
    public bool canWallRun = false;        // Correr en paredes
    public bool canGlide = false;          // Planear
    public bool canSprint = false;         // Sprint o Carrera acelerada
    [Header("Abilities Values")]
    public float sprintSpeed = 10f;         // Velocidad durante el sprint
    public float dashSpeed = 30f;           // Velocidad del dash
    public float dashDuration = 0.15f;      // Duración del dash
    public float dashCooldown = 0.75f;      // Tiempo entre dashes
    public bool allowAirDash = true;        // ¿Permitir dash en el aire?
    public float wallJumpForce = 15f;       // Fuerza del salto en pared
    public float wallRunGravity = 1f;       // Gravedad reducida en wall run
    public float wallRunSpeed = 11f;        // Velocidad durante wall run
    public float glideFallSpeed = -1.75f;   // Velocidad de caída al planear
    // =================================================
    // VARIABLES PRIVADAS
    // =================================================
    
    private CharacterController controller; // Componente CharacterController
    private Vector3 velocity;               // Velocidad actual del jugador
    private bool isGrounded;                // ¿Está tocando el suelo?
    private int jumpCount = 0;              // Contador para doble salto
    private bool isDashing = false;         // ¿Actualmente en dash?
    private float dashTimer = 0f;           // Temporizador para el dash
    private bool isWallRunning = false;     // ¿Actualmente en wall run?
    private bool isGliding = false;         // ¿Actualmente planeando?
    private Vector3 wallNormal;             // Normal de la pared actual
    private bool isTouchingWall = false;    // ¿Tocando una pared?
    private float lastDashTime = -10f;      // Tiempo del último dash
    private bool hasAirDashed = false;      // ¿Ya se usó dash en el aire?
    // =================================================
    // INICIALIZACIÓN
    // =================================================
    
    void Start()
    {
        //Asignamos el componente Character Controller a una variable para poder manipularlo
        controller = GetComponent<CharacterController>();
        //Hacemos que el cursor sea invisible
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }
    // =================================================
    // BUCLE PRINCIPAL
    // =================================================
    
    void Update()
    {
        // Verificar estados básicos
        CheckGround();
        CheckWalls();
        // Manejar habilidades
        HandleMovement();
        HandleJump();
        HandleDash();
        HandleWallRun();
        HandleGlide();
    }
    // =================================================
    // MÉTODOS DE DETECCIÓN
    // =================================================
    
    
    /// Verifica si el jugador está tocando el suelo    
    private void CheckGround()
    {
        // Detectar colisión con el suelo usando una esfera física
        // - Parámetros:
        //   * Posición: Usa la posición del objeto groundCheck (pies del personaje)
        //   * Radio: groundCheckRadius (tamaño de la esfera de detección)
        //   * Capa: groundMask (solo detecta colisiones con capas definidas como suelo)
        isGrounded = Physics.CheckSphere(groundCheck.position, groundCheckRadius, groundMask);
        // Si está en el suelo, resetear variables de estado
        if (isGrounded)
        {
            // Resetear contador de saltos (permite nuevo salto básico)
            jumpCount = 0;
            // Reactivar dash aéreo (permite usar dash en el próximo salto)
            hasAirDashed = false;
            // Resetear velocidad horizontal solo si NO está en dash
            // - Evita interrumpir el impulso del dash
            // - Mantiene momentum si está dashando en el suelo
            if(!isDashing){
                velocity.x = 0; // Detener movimiento en eje X
                velocity.z = 0; // Detener movimiento en eje Z
            }
            
        }
    }
    
    /// Detecta paredes cercanas en 4 direcciones principales y almacena su normal usando SphereCast    
    private void CheckWalls()
    {
         isTouchingWall = false; // Inicializar estado de contacto con pared
        float detectionDistance = 1.2f; // Configurar parámetros de detección
        
        // Direcciones a verificar (frontal, derecha, izquierda, trasera)
        Vector3[] directions = new Vector3[]
        {
            transform.forward, // Dirección frontal (Z positivo)
            transform.right,   // Dirección derecha (X positivo)
            -transform.right,  // Dirección izquierda (X negativo)
            -transform.forward // Dirección trasera (Z negativo)
        };
         // Recorrer todas las direcciones definidas
        RaycastHit hit;
        foreach(Vector3 dir in directions)
        {
            // Realizar SphereCast en dirección actual
            if(Physics.SphereCast(
                transform.position, // Origen en centro del personaje
                0.5f,               // Radio de la esfera (mitad del ancho del personaje)
                dir,                // Dirección actual del chequeo
                out hit,            // Información de la colisión
                detectionDistance,  // Distancia máxima de detección
                groundMask          // Solo detectar capas definidas como suelo/pared
            ) && Vector3.Angle(hit.normal, Vector3.up) > 50f) // Excluir pisos
            // Filtrar superficies planas (pisos/techos)
            // - Calcula ángulo entre la normal de la superficie y el eje vertical
            // - Si >50°, se considera pared (evita detectar pisos inclinados)
            {
                isTouchingWall = true; // Indicar contacto con pared
                wallNormal = hit.normal; // Almacenar normal de la pared
                return; // Salir del método tras primera detección válida
            }
        }
    }
    // =================================================
    // SISTEMA DE MOVIMIENTO
    // =================================================
    
    
    /// Maneja el movimiento básico y el sprint    
    private void HandleMovement()
    {
        if (isDashing) return; // Ignorar movimiento normal durante el dash
        // Obtener inputs de ejes horizontales/verticales (teclas WASD o joystick y Valores entre [-1, 1])
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        
        // Calcular dirección de movimiento relativa a la cámara
        // - Convierte inputs en vector 3D orientado según la rotación de la cámara
        Vector3 moveDirection = CalculateCameraMovement(horizontal, vertical);
        
        // Determinar velocidad actual considerando sprint
        // - Usa sprintSpeed si se mantiene el botón de sprint y está habilitado
        // - Mantiene moveSpeed como valor base en otros casos
        float currentSpeed = canSprint && Input.GetButton("Sprint") ? sprintSpeed : moveSpeed;
         // Aplicar movimiento horizontal solo si no está en wall run
        // - Evita conflicto entre sistemas de movimiento
        if(!isWallRunning)
        {
            // Mueve el CharacterController en la dirección calculada
            // - Multiplica por deltaTime para hacerlo frame-rate independent
            controller.Move(moveDirection * currentSpeed * Time.deltaTime);
        }
        // Calcular gravedad según estado (ascenso/descenso)
        // - fallingGravity: Gravedad fuerte durante caída o en el aire
        // - risingGravity: Gravedad suave durante ascenso
        float currentGravity = (velocity.y < 0 || !isGrounded) ? fallingGravity : risingGravity ;
        // Aplicar gravedad solo si no está en wall run/glide
        if(!isWallRunning && !isGliding)
        {
            velocity.y += currentGravity * Time.deltaTime;
        }
        
        // Aplicar velocidad vertical acumulada
        // - Movimiento independiente del horizontal para mejor control
        controller.Move(velocity * Time.deltaTime);
    }
    
    /// Calcula dirección de movimiento relativa a la cámara    
    private Vector3 CalculateCameraMovement(float horizontal, float vertical)
    {
        // Obtener vectores direccionales base de la cámara
        Vector3 cameraForward = cameraTransform.forward; // Vector hacia donde apunta la cámara
        Vector3 cameraRight = cameraTransform.right; // Vector derecho de la cámara
        
        // Aplanar vectores para ignorar componente vertical (eje Y)
        // - Evita movimiento no deseado al mirar hacia arriba/abajo
        // Mantener solo componentes X y Z
        cameraForward.y = 0;
        cameraRight.y = 0;
        
        // Normalizar vectores para mantener velocidad constante
        // - Evita que movimientos diagonales sean más rápidos
        cameraForward.Normalize(); // Longitud = 1
        cameraRight.Normalize();
        // Combinar direcciones según input del jugador
        // - Vertical: Controla movimiento adelante/atrás (eje Z)
        // - Horizontal: Controla movimiento lateral (eje X)
        Vector3 combinedDirection = (cameraForward * vertical) + (cameraRight * horizontal);
        // Normalizar resultado final para:
        // - Mantener velocidad constante en todas las direcciones
        // - Evitar valores mayores a 1 en movimiento diagonal
        return combinedDirection.normalized;
    }
    // =================================================
    // SISTEMA DE SALTO
    // =================================================
    
    
    /// Maneja todos los tipos de salto    
    private void HandleJump()
    {
        // Detectar que el botón de Salto ha sido oprimido
        if(Input.GetButtonDown("Jump"))
        {
            isGliding = false; // Cancelar cualquier estado de planeo activo
            // Salto normal/doble salto
            if(isGrounded || (canDoubleJump && jumpCount < 1))
            {
                // Fórmula física para altura de salto: v = √(2 * fuerza * gravedad)
                // - risingGravity se usa para cálculo consistente con el sistema de gravedad
                velocity.y = Mathf.Sqrt(jumpForce * -2 * risingGravity);
                jumpCount++; // Incrementar contador de saltos aéreos
            }
            // Wall jump
            else if(canWallJump && isTouchingWall)
            {
                // Calcular dirección óptima (alejarse de la pared + vertical)
                // - Vector.up * 2: Impulso vertical predominante
                // - wallNormal: Dirección perpendicular a la pared para empuje horizontal
                Vector3 jumpDirection = (Vector3.up * 2f + wallNormal).normalized;
                
                // Aplicar fuerza con impulso
                velocity = jumpDirection * wallJumpForce;
                
                // Resetear estados
                jumpCount = 0;
                hasAirDashed = false; // Reactivar dash aéreo
                
                // Rotar personaje para mirar en dirección opuesta a la pared
                // - LookRotation(-wallNormal): Crea rotación alejándose de la pared
                transform.rotation = Quaternion.LookRotation(-wallNormal);
            }
        }
    }
    // =================================================
    // SISTEMA DE DASH
    // =================================================
    
    
    /// Maneja el movimiento de dash    
    private void HandleDash()
    {
        // ¿Puede dashar en el aire? (Habilidad activa + en aire + no ha usado dash aéreo)
        bool canAirDash = allowAirDash && !isGrounded && !hasAirDashed;
        // ¿Puede dashar en suelo? (En suelo + cooldown completado)
        bool canGroundDash = isGrounded && Time.time >= lastDashTime + dashCooldown;
        
        // Iniciar Dash si se oprime el botón y se cumplen las condiciones
        if (canDash && Input.GetButtonDown("Fire2") && (canGroundDash || canAirDash))
        {
            // Configurar variables
            isDashing = true; // Activar flag de dash
            dashTimer = dashDuration; // Iniciar temporizador
            lastDashTime = Time.time; // Registrar momento del último dash
            
            // Registrar dash aéreo (solo una vez por salto)
            if(!isGrounded) hasAirDashed = true;
            // Calcular dirección del dash
            Vector3 dashDirection = GetDashDirection();
            // Aplicar fuerza del dash (combinar con velocidad vertical existente)
            velocity = dashDirection * dashSpeed; // Velocidad en dirección calculada
            velocity.y = isGrounded ? 0 : velocity.y; // En suelo: dash puramente horizontal
        }
        // Ejecutar lógica durante el dash
        if (isDashing)
        {
            dashTimer -= Time.deltaTime; // Reducir temporizador
            // Finalizar dash cuando el tiempo expire
            if (dashTimer <= 0)
            {
                isDashing = false;
                velocity = Vector3.zero; // Eliminar velocidad residual
            }
            //Aplicar movimiento del dash (Movimiento frame-rate independent)
            controller.Move(velocity * Time.deltaTime);
        }
    }
    // Calcula la dirección del dash basada en el input y la cámara
    private Vector3 GetDashDirection()
    {
        // Obtener input RAW (sin suavizado de Unity) para dirección inmediata
        float horizontal = Input.GetAxisRaw("Horizontal"); // Valores: -1, 0, 1
        float vertical = Input.GetAxisRaw("Vertical"); // Valores: -1, 0, 1
        
        // Calcular dirección relativa a la cámara (igual que el movimiento normal)
        Vector3 cameraForward = cameraTransform.forward;
        Vector3 cameraRight = cameraTransform.right;
        // Ignorar componente vertical para movimiento horizontal
        cameraForward.y = 0f;
        cameraRight.y = 0f;
        // Normalizar vectores para mantener velocidad constante
        cameraForward.Normalize();
        cameraRight.Normalize();
        // Combinar direcciones según input y normalizar para evitar velocidad extra en diagonales
        Vector3 dashDirection = (cameraForward * vertical + cameraRight * horizontal).normalized;
        // Si no hay input, usar dirección frontal de la cámara
        if(dashDirection == Vector3.zero)
        {
            dashDirection = cameraForward; // Dash hacia adelante por defecto
        }
        return dashDirection;
    }
    // =================================================
    // SISTEMA DE WALL RUN
    // =================================================
    
    
    /// Maneja el movimiento en paredes    
    private void HandleWallRun()
    {   
        // Iniciar el wall run si se oprime el botón correspondiente y se cumplen las condiciones
        if(canWallRun && !isGrounded && isTouchingWall && Input.GetButton("Sprint"))
        {
            isWallRunning = true;
            // Rotación automática hacia la pared:
            // - Interpola suavemente la rotación actual hacia la dirección opuesta a la normal de la pared
            // - Quaternion.LookRotation(-wallNormal): Crea rotación mirando en dirección contraria a la pared
            // - 10f * Time.deltaTime: Velocidad de rotación (10 grados/segundo)
            transform.rotation = Quaternion.Lerp(
            transform.rotation,
            Quaternion.LookRotation(-wallNormal),
            10f * Time.deltaTime
            );
            
            // Calcular direcciones de movimiento:
            // Dirección "frontal" paralela a la pared (90° respecto a la normal)
            Vector3 wallForward = Vector3.Cross(wallNormal, Vector3.up);
            // Proyectar dirección de la cámara sobre el plano de la pared
            // - Vector3.ProjectOnPlane: Elimina componente perpendicular a la pared
            // - .normalized: Mantiene velocidad constante
            Vector3 cameraRelative = Vector3.ProjectOnPlane(cameraTransform.forward,wallNormal).normalized;
            
            // Mover en dirección combinada (pared + input)
            float horizontal = Input.GetAxis("Horizontal");
            float vertical = Input.GetAxis("Vertical");
            // Combinar direcciones:
            // - vertical * cameraRelative: Movimiento hacia adelante/atrás relativo a la cámara
            // - horizontal * transform.right: Movimiento lateral relativo a la rotación del personaje
            Vector3 moveDirection = (cameraRelative * vertical + transform.right * horizontal) * wallRunSpeed;
            
            // Aplicar movimiento:
            controller.Move(moveDirection * Time.deltaTime); // Movimiento horizontal
            // Configurar gravedad vertical personalizada:
            // - Valor negativo para mantener al personaje "pegado" a la pared
            // - wallRunGravity controla la velocidad de deslizamiento
            velocity.y = -wallRunGravity;
        }
        else
        {
            isWallRunning = false; // Desactivar wall run si no se cumplen las condiciones
        }
    }
    // =================================================
    // SISTEMA DE PLANEO
    // =================================================
    
    
    /// Maneja el planeo aéreo    
    private void HandleGlide()
    {
        //Iniciar el planeo si se mantiene oprimido el botón correspondiente y se cumplen las condiciones requeridas
        if (canGlide && !isGrounded && Input.GetButton("Jump") && !isGliding && velocity.y<0)
        {
            isGliding = true;
            velocity.y = glideFallSpeed; //Se modifica la velocidad en el eje vertical para usar el valor definido
        }
    // Cancelar planeo al tocar el suelo o soltar el botón
    else if (isGrounded || Input.GetButtonUp("Jump"))
        {
            isGliding = false;
        }
    }
}