using System;
using System.Collections.Generic;
using nkast.Aether.Physics2D.Common;
using tainicom.Aether.Physics2D.Fluids;
using CtrEditor.Simulacion.Fluids.Components;

namespace CtrEditor.Simulacion.Fluids.Components
{
    /// <summary>
    /// Clase base para todos los componentes de fluidos
    /// </summary>
    public abstract class ComponenteFluido : IContenedorFluido
    {
        public Vector2 Posicion { get; set; }
        public bool EsActivo { get; set; } = true;
        
        /// <summary>
        /// Convierte una posición de metros/WPF a sistema de coordenadas de FluidSystem2
        /// </summary>
        protected Vector2 ConvertirPosicion(Vector2 posicion, int worldWidth)
        {
            return new Vector2(
                posicion.X * 100 - worldWidth/2,
                posicion.Y * 100
            );
        }
        
        /// <summary>
        /// Implementación en clases derivadas para restricciones específicas
        /// </summary>
        public abstract void RestringirParticula(Particle particula);
    }
    
    /// <summary>
    /// Tanque para contener fluidos
    /// </summary>
    public class Tanque : ComponenteFluido
    {
        public float Ancho { get; set; }
        public float Alto { get; set; }
        private int _worldWidth;
        
        // Coordenadas ajustadas al sistema de FluidSystem2
        private float _xMin, _xMax, _yMin, _yMax;
        private float _coefRebote = 0.3f; // Factor de rebote en colisiones
        
        /// <summary>
        /// Constructor para un tanque
        /// </summary>
        /// <param name="posicion">Posición del centro del tanque</param>
        /// <param name="ancho">Ancho del tanque</param>
        /// <param name="alto">Alto del tanque</param>
        /// <param name="worldWidth">Ancho del mundo en cm (para conversión de coordenadas)</param>
        public Tanque(Vector2 posicion, float ancho, float alto, int worldWidth)
        {
            Posicion = posicion;
            Ancho = ancho;
            Alto = alto;
            _worldWidth = worldWidth;
            
            ActualizarLimites();
        }
        
        /// <summary>
        /// Actualiza los límites internos del tanque después de cambiar la posición o tamaño
        /// </summary>
        public void ActualizarLimites()
        {
            // Convertir a coordenadas internas
            _xMin = (Posicion.X - Ancho/2) * 100 - _worldWidth/2;
            _xMax = (Posicion.X + Ancho/2) * 100 - _worldWidth/2;
            _yMin = Posicion.Y * 100;
            _yMax = (Posicion.Y + Alto) * 100;
        }
        
        /// <summary>
        /// Restringe las partículas para que permanezcan dentro del tanque
        /// </summary>
        public override void RestringirParticula(Particle particula)
        {
            if (!EsActivo) return;
            
            // Comprobar si la partícula está dentro del área del tanque
            bool dentroX = particula.Position.X >= _xMin && particula.Position.X <= _xMax;
            bool dentroY = particula.Position.Y >= _yMin && particula.Position.Y <= _yMax;
            
            if (dentroX && dentroY)
            {
                // La partícula está dentro del tanque, comprobar si está cerca de los bordes
                if (particula.Position.X < _xMin + 5)
                {
                    particula.Position.X = _xMin + 5;
                    particula.Velocity.X *= -_coefRebote;
                }
                else if (particula.Position.X > _xMax - 5)
                {
                    particula.Position.X = _xMax - 5;
                    particula.Velocity.X *= -_coefRebote;
                }
                
                if (particula.Position.Y < _yMin + 5)
                {
                    particula.Position.Y = _yMin + 5;
                    particula.Velocity.Y *= -_coefRebote;
                }
                else if (particula.Position.Y > _yMax - 5)
                {
                    particula.Position.Y = _yMax - 5;
                    particula.Velocity.Y *= -_coefRebote;
                }
            }
        }
        
        /// <summary>
        /// Obtiene el nivel de llenado del tanque en porcentaje
        /// </summary>
        /// <param name="simulacion">Instancia activa de la simulación</param>
        /// <returns>Porcentaje de llenado (0-1)</returns>
        public float ObtenerNivelLlenado()
        {
            // Sería necesario acceder a las partículas para calcular esto
            // Implementación en clases derivadas específicas
            return 0.0f;
        }
    }
    
    /// <summary>
    /// Representa un segmento de tubo para fluidos
    /// </summary>
    public class SegmentoTubo
    {
        public Vector2 Inicio { get; }
        public Vector2 Fin { get; }
        public float Radio { get; }
        
        public SegmentoTubo(Vector2 inicio, Vector2 fin, float radio)
        {
            Inicio = inicio;
            Fin = fin;
            Radio = radio;
        }
        
        /// <summary>
        /// Contiene y restringe el movimiento de una partícula dentro del tubo
        /// </summary>
        /// <returns>Verdadero si se aplicó alguna restricción</returns>
        public bool ContenerParticula(Particle particula)
        {
            // Calcular distancia de la partícula al segmento de línea
            Vector2 pos = new Vector2(particula.Position.X, particula.Position.Y);
            float distancia = DistanciaPuntoALinea(pos, Inicio, Fin);
            
            // Si la distancia es mayor que el radio, aplicar restricción
            if (distancia > Radio)
            {
                // Calcular punto más cercano en el segmento
                Vector2 puntoMasCercano = PuntoMasCercanoEnLinea(pos, Inicio, Fin);
                
                // Dirección desde partícula hacia punto más cercano en el centro del tubo
                Vector2 direccion = VectorUtils.NormalizeVector(puntoMasCercano - pos);
                
                // Mover la partícula al interior del tubo
                particula.Position.X = puntoMasCercano.X - direccion.X * Radio * 0.9f;
                particula.Position.Y = puntoMasCercano.Y - direccion.Y * Radio * 0.9f;
                
                // Ajustar velocidad para simular rebote
                float velocidadNormal = VectorUtils.DotProduct(
                    new Vector2(particula.Velocity.X, particula.Velocity.Y), 
                    direccion);
                
                particula.Velocity.X -= direccion.X * velocidadNormal * 1.8f;
                particula.Velocity.Y -= direccion.Y * velocidadNormal * 1.8f;
                
                return true;
            }
            
            return false; // La partícula está dentro del tubo
        }
        
        /// <summary>
        /// Calcula la distancia de un punto a una línea
        /// </summary>
        private float DistanciaPuntoALinea(Vector2 punto, Vector2 lineaInicio, Vector2 lineaFin)
        {
            // Vector que representa la dirección de la línea
            Vector2 linea = lineaFin - lineaInicio;
            float longitud = linea.Length();
            
            if (longitud < 0.0001f)
                return VectorUtils.DistanceBetween(punto, lineaInicio); // Es un punto, no una línea
                
            // Normalizar el vector de la línea
            Vector2 lineaNormalizada = linea / longitud;
            
            // Vector desde el inicio de la línea hasta el punto
            Vector2 puntoDesdeInicio = punto - lineaInicio;
            
            // Proyectar puntoDesdeInicio sobre la línea
            float proyeccion = VectorUtils.DotProduct(puntoDesdeInicio, lineaNormalizada);
            
            // Limitar la proyección al segmento de línea
            proyeccion = Math.Max(0, Math.Min(longitud, proyeccion));
            
            // Punto más cercano en la línea
            Vector2 puntoMasCercano = lineaInicio + lineaNormalizada * proyeccion;
            
            // Distancia desde el punto hasta el punto más cercano
            return VectorUtils.DistanceBetween(punto, puntoMasCercano);
        }
        
        /// Calcula el punto más cercano en una línea desde un punto dado
        /// </summary>
        private Vector2 PuntoMasCercanoEnLinea(Vector2 punto, Vector2 lineaInicio, Vector2 lineaFin)
        {
            // Vector que representa la dirección de la línea
            Vector2 linea = lineaFin - lineaInicio;
            float longitud = linea.Length();

            if (longitud < 0.0001f)
                return lineaInicio; // Es un punto, no una línea

            // Normalizar el vector de la línea
            Vector2 lineaNormalizada = linea / longitud;

            // Vector desde el inicio de la línea hasta el punto
            Vector2 puntoDesdeInicio = punto - lineaInicio;

            // Proyectar puntoDesdeInicio sobre la línea
            float proyeccion;
            Vector2.Dot(ref puntoDesdeInicio, ref lineaNormalizada, out proyeccion);

            // Limitar la proyección al segmento de línea
            proyeccion = Math.Max(0, Math.Min(longitud, proyeccion));

            // Punto más cercano en la línea
            return lineaInicio + lineaNormalizada * proyeccion;
        }
    }
    
    /// <summary>
    /// Tubería para conducir fluidos entre puntos
    /// </summary>
    public class Tuberia : ComponenteFluido
    {
        private List<SegmentoTubo> _segmentos = new List<SegmentoTubo>();
        private List<Vector2> _puntos = new List<Vector2>();
        public float Diametro { get; private set; }
        private int _worldWidth;
        
        /// <summary>
        /// Constructor para una tubería
        /// </summary>
        /// <param name="diametro">Diámetro de la tubería</param>
        /// <param name="worldWidth">Ancho del mundo en cm (para conversión de coordenadas)</param>
        public Tuberia(float diametro, int worldWidth)
        {
            Diametro = diametro;
            _worldWidth = worldWidth;
        }
        
        /// <summary>
        /// Agrega un punto a la tubería, creando segmentos automáticamente
        /// </summary>
        public void AgregarPunto(Vector2 punto)
        {
            // Convertir punto a coordenadas internas
            Vector2 puntoAjustado = ConvertirPosicion(punto, _worldWidth);
            
            // Si ya hay puntos, crear un segmento con el punto anterior
            if (_puntos.Count > 0)
            {
                _segmentos.Add(new SegmentoTubo(
                    _puntos[_puntos.Count - 1],
                    puntoAjustado,
                    Diametro * 100 / 2 // Radio en unidades internas
                ));
            }
            
            _puntos.Add(puntoAjustado);
        }
        
        /// <summary>
        /// Restringe las partículas para que permanezcan dentro de la tubería
        /// </summary>
        public override void RestringirParticula(Particle particula)
        {
            if (!EsActivo) return;
            
            // Comprobar si la partícula está cerca de la tubería
            Vector2 posParticula = new Vector2(particula.Position.X, particula.Position.Y);
            float distanciaMinima = float.MaxValue;
            SegmentoTubo segmentoMasCercano = null;
            
            // Encontrar el segmento más cercano
            foreach (var segmento in _segmentos)
            {
                float distancia = DistanciaPuntoASegmento(posParticula, segmento.Inicio, segmento.Fin);
                if (distancia < distanciaMinima)
                {
                    distanciaMinima = distancia;
                    segmentoMasCercano = segmento;
                }
            }
            
            // Aplicar restricciones si hay un segmento cercano
            if (segmentoMasCercano != null)
            {
                segmentoMasCercano.ContenerParticula(particula);
            }
        }
        
        /// <summary>
        /// Calcula la distancia desde un punto a un segmento de línea
        /// </summary>
        private float DistanciaPuntoASegmento(Vector2 punto, Vector2 segmentoInicio, Vector2 segmentoFin)
        {
            // Vector dirección del segmento
            Vector2 segmento = segmentoFin - segmentoInicio;
            float longitudCuadrada = segmento.LengthSquared();
            
            if (longitudCuadrada < 0.0001f)
                return VectorUtils.DistanceBetween(punto, segmentoInicio); // Es un punto, no un segmento
                
            // Calcular proyección del punto sobre el segmento
            float t = VectorUtils.DotProduct(punto - segmentoInicio, segmento) / longitudCuadrada;
            t = Math.Max(0, Math.Min(1, t));
            
            // Punto más cercano en el segmento
            Vector2 proyeccion = segmentoInicio + t * segmento;
            
            // Distancia desde el punto hasta la proyección
            return VectorUtils.DistanceBetween(punto, proyeccion);
        }
    }
    
    /// <summary>
    /// Válvula para controlar el flujo de fluidos
    /// </summary>
    public class Valvula : ComponenteFluido
    {
        public float Apertura { get; set; } // 0.0 = cerrada, 1.0 = abierta
        public float Diametro { get; set; }
        private int _worldWidth;
        private Vector2 _posicionAjustada;
        
        /// <summary>
        /// Constructor para una válvula
        /// </summary>
        /// <param name="posicion">Posición de la válvula</param>
        /// <param name="diametro">Diámetro del conducto de la válvula</param>
        /// <param name="apertura">Apertura inicial (0-1)</param>
        /// <param name="worldWidth">Ancho del mundo en cm (para conversión de coordenadas)</param>
        public Valvula(Vector2 posicion, float diametro, float apertura, int worldWidth)
        {
            Posicion = posicion;
            Diametro = diametro;
            Apertura = Math.Clamp(apertura, 0, 1);
            _worldWidth = worldWidth;
            
            _posicionAjustada = ConvertirPosicion(posicion, worldWidth);
        }
        
        /// <summary>
        /// Actualiza la posición ajustada después de cambiar la posición
        /// </summary>
        public void ActualizarPosicion(Vector2 nuevaPosicion)
        {
            Posicion = nuevaPosicion;
            _posicionAjustada = ConvertirPosicion(nuevaPosicion, _worldWidth);
        }
        
        /// <summary>
        /// Restringe y modula el flujo de partículas a través de la válvula
        /// </summary>
        public override void RestringirParticula(Particle particula)
        {
            if (!EsActivo) return;
            
            // Calcular distancia al centro de la válvula
            Vector2 posParticula = new Vector2(particula.Position.X, particula.Position.Y);
            float distancia = VectorUtils.DistanceBetween(_posicionAjustada, posParticula);
            float radioValvula = Diametro * 100 / 2; // Radio en unidades internas
            
            // Verificar si la partícula está dentro del radio de acción de la válvula
            if (distancia < radioValvula * 1.2)
            {
                // Si la válvula está cerrada o casi cerrada, bloquear paso
                if (Apertura < 0.05f)
                {
                    // Rechazar partícula
                    Vector2 direccion = VectorUtils.NormalizeVector(posParticula - _posicionAjustada);
                    particula.Position.X = _posicionAjustada.X + direccion.X * (radioValvula * 1.2f);
                    particula.Position.Y = _posicionAjustada.Y + direccion.Y * (radioValvula * 1.2f);
                    
                    // Reducir velocidad significativamente
                    particula.Velocity.X *= 0.1f;
                    particula.Velocity.Y *= 0.1f;
                }
                // Si está parcialmente abierta, reducir velocidad proporcionalmente
                else if (Apertura < 0.9f)
                {
                    // Calcular factor de reducción basado en la apertura
                    float factorReduccion = Apertura * Apertura; // Relación no lineal
                    
                    // Aplicar resistencia proporcional a la apertura
                    particula.Velocity.X *= factorReduccion;
                    particula.Velocity.Y *= factorReduccion;
                }
            }
        }
    }
}
