using System;
using FluidSim.Components;
using FluidSim.Interfaces;

namespace FluidSim.Core
{
    public class OrificeLink
    {
        public Port? VolumePort { get; }
        public Pipe1D Pipe { get; }
        public bool IsPipeLeftEnd { get; }
        public Func<double> AreaProvider { get; set; }
        public double DischargeCoefficient { get; set; } = 0.62;

        public double EffectiveLength { get; set; } = 0.001;
        public bool UseInertance { get; set; } = false;

        // Current mass flow (kg/s, positive = volume → pipe)
        private double _mdot;

        public double LastMassFlowRate { get; private set; }   // positive = into volume
        public double LastFaceDensity { get; private set; }
        public double LastFaceVelocity { get; private set; }
        public double LastFacePressure { get; private set; }

        public OrificeLink(Port? volumePort, Pipe1D pipe, bool isPipeLeftEnd, Func<double> areaProvider)
        {
            VolumePort = volumePort;
            Pipe = pipe ?? throw new ArgumentNullException(nameof(pipe));
            IsPipeLeftEnd = isPipeLeftEnd;
            AreaProvider = areaProvider ?? throw new ArgumentNullException(nameof(areaProvider));
            _mdot = 0.0;
        }

        public void Resolve(double dtSub)
        {
            double area = AreaProvider();
            if (area < 1e-12 || VolumePort == null)
            {
                SetClosedWall();
                return;
            }

            // Gather states
            double volP = VolumePort.Pressure;
            double volRho = VolumePort.Density;
            double volT = VolumePort.Temperature;
            double volH = VolumePort.SpecificEnthalpy;
            double volAF = VolumePort.AirFraction;

            (double pipeRho, double pipeU, double pipeP) = IsPipeLeftEnd
                ? Pipe.GetInteriorStateLeft()
                : Pipe.GetInteriorStateRight();

            double pipeT = pipeP / Math.Max(pipeRho * 287.0, 1e-12);
            double pipeAF = IsPipeLeftEnd
                ? Pipe.GetInteriorAirFractionLeft()
                : Pipe.GetInteriorAirFractionRight();

            double gamma = 1.4;
            double R = 287.0;

            // ---- Steady‑state nozzle solution ----
            double mdotSS;   // positive = volume → pipe
            double rhoFace0, uFace0, pFace0;
            if (volP >= pipeP)
            {
                IsentropicOrifice.Compute(volP, volRho, volT, pipeP, gamma, R, area, DischargeCoefficient,
                    out double mdotUpToDown, out rhoFace0, out uFace0, out pFace0);
                mdotSS = mdotUpToDown;
            }
            else
            {
                IsentropicOrifice.Compute(pipeP, pipeRho, pipeT, volP, gamma, R, area, DischargeCoefficient,
                    out double mdotUpToDown, out rhoFace0, out uFace0, out pFace0);
                mdotSS = -mdotUpToDown;
            }

            // ---- Dynamic update ----
            if (UseInertance)
            {
                double rhoUp = _mdot >= 0 ? volRho : pipeRho;
                double inertance = rhoUp * EffectiveLength / area;
                double dp = volP - pipeP;
                double resistance = Math.Abs(dp) / Math.Max(Math.Abs(mdotSS), 1e-12);
                double dmdot_dt = (dp - resistance * _mdot) / inertance;
                _mdot += dmdot_dt * dtSub;
            }
            else
            {
                _mdot = mdotSS;
            }

            // Clamp outflow to available mass (if finite volume)
            if (VolumePort.Owner is Volume0D vol)
            {
                double maxOut = vol.Mass / dtSub;
                if (_mdot > maxOut) _mdot = maxOut;
            }

            // ---- Ghost state with air fraction ----
            double rhoFace = _mdot >= 0 ? volRho : pipeRho;
            double pFace = pFace0;
            double mdotMag = Math.Abs(_mdot);
            double uFace = mdotMag / (rhoFace * area);

            // Determine air fraction for ghost and for volume port
            double airFracGhost;   // air fraction of ghost cell (at pipe end)
            double airFracForVolume; // if flow reverses into volume, this is the air fraction entering volume

            if (_mdot >= 0)   // volume → pipe
            {
                airFracGhost = volAF;
                // Flow enters pipe; no need to set volume's air fraction (port already has its own)
                airFracForVolume = volAF; // unused
            }
            else              // pipe → volume
            {
                airFracGhost = pipeAF;
                airFracForVolume = pipeAF;
                VolumePort.AirFraction = airFracForVolume;
            }

            if (IsPipeLeftEnd)
                uFace = _mdot >= 0 ? uFace : -uFace;
            else
                uFace = _mdot >= 0 ? -uFace : uFace;

            if (IsPipeLeftEnd)
                Pipe.SetGhostLeft(rhoFace, uFace, pFace, airFracGhost);
            else
                Pipe.SetGhostRight(rhoFace, uFace, pFace, airFracGhost);

            // Store results (positive = into volume)
            LastMassFlowRate = -_mdot;
            LastFaceDensity = rhoFace;
            LastFaceVelocity = uFace;
            LastFacePressure = pFace;

            VolumePort.MassFlowRate = -_mdot;

            // Enthalpy transport
            if (-_mdot >= 0)   // inflow → pipe enthalpy
            {
                double hPipe = gamma / (gamma - 1.0) * pipeP / Math.Max(pipeRho, 1e-12);
                VolumePort.SpecificEnthalpy = hPipe;
            }
            else
            {
                VolumePort.SpecificEnthalpy = volH;
            }
        }

        private void SetClosedWall()
        {
            var (rInt, uInt, pInt) = IsPipeLeftEnd
                ? Pipe.GetInteriorStateLeft()
                : Pipe.GetInteriorStateRight();

            if (IsPipeLeftEnd)
                Pipe.SetGhostLeft(rInt, -uInt, pInt, IsPipeLeftEnd ? Pipe.GetInteriorAirFractionLeft() : Pipe.GetInteriorAirFractionRight());
            else
                Pipe.SetGhostRight(rInt, -uInt, pInt, IsPipeLeftEnd ? Pipe.GetInteriorAirFractionLeft() : Pipe.GetInteriorAirFractionRight());

            LastMassFlowRate = 0.0;
            LastFaceDensity = rInt;
            LastFaceVelocity = 0.0;
            LastFacePressure = pInt;
            if (VolumePort != null)
                VolumePort.MassFlowRate = 0.0;
        }
    }
}