namespace ReactorMaintenance.Simulation;

internal static class PlayerActionSystem
{
    public static LevelState MoveRobot(LevelState level, GridPosition destination, Func<LevelState, LevelState> spendAction)
    {
        if (!CanSpendAction(level) || !level.IsFloor(destination) || level.Robot.Position.ManhattanDistance(destination) != 1)
            return Refuse(level, "MOVE BLOCKED");

        return spendAction(level with {
            Robot = level.Robot with {
                Position = destination,
                HeatImmunitySteps = Math.Max(0, level.Robot.HeatImmunitySteps - 1)
            }
        });
    }

    public static LevelState InteractProp(LevelState level, Func<LevelState, LevelState> spendAction)
    {
        if (!CanSpendAction(level))
            return Refuse(level, "NO ACTIONS");

        var position = level.Robot.Position;
        var prop = level.GetProp(position);
        if (prop.Type == EPropType.None)
            return Refuse(level, "NO PROP");

        var next = prop.Type switch {
            EPropType.Flow or EPropType.Consumer => ToggleProp(level, position, prop),
            EPropType.Junction => CycleJunctionMode(level, position, prop),
            EPropType.Door => ToggleDoor(level, position),
            EPropType.AllSeeingEyeTerminal => level with { Global = level.Global with { AllSeeingEyeUnlocked = true, Status = "ALL-SEEING-EYE ONLINE" } },
            EPropType.RemedySupply => PickUpRemedy(level, position, prop),
            EPropType.ReactorControl => ReactorSystem.Activate(level),
            _ => level
        };

        return spendAction(next);
    }

    public static LevelState InteractLeak(LevelState level, ECarrierType carrier, bool useRemedy, Func<LevelState, LevelState> spendAction)
    {
        if (!CanSpendAction(level))
            return Refuse(level, "NO ACTIONS");

        var leakIndex = level.Leaks.ToList().FindIndex(leak => !leak.Repaired && leak.Carrier == carrier && leak.AccessPosition == level.Robot.Position);
        if (leakIndex < 0)
            return Refuse(level, "NO REACHABLE LEAK");

        var leak = level.Leaks[leakIndex];
        var next = useRemedy ? ApplyElementRemedy(level, leak) : RepairLeak(level, leakIndex, leak);
        return spendAction(next);
    }

    public static LevelState ApplyHeatShield(LevelState level, Func<LevelState, LevelState> spendAction)
    {
        if (!CanSpendAction(level) || level.Robot.HeatShields <= 0)
            return Refuse(level, "NO HEAT SHIELD");

        return spendAction(level with {
            Robot = level.Robot.Spend(ERemedyType.HeatShield) with { HeatImmunitySteps = Balancing.Current.HeatShieldSteps }
        });
    }

    private static LevelState ToggleProp(LevelState level, GridPosition position, PropState prop)
    {
        var switchState = prop.SwitchState == EPropSwitchState.Enabled ? EPropSwitchState.Disabled : EPropSwitchState.Enabled;
        return level.SetProp(position, prop with { SwitchState = switchState });
    }

    private static LevelState ToggleDoor(LevelState level, GridPosition position)
    {
        var doors = level.Doors.ToArray();
        var index = Array.FindIndex(doors, door => door.A == position || door.B == position);
        if (index < 0)
            return level;

        doors[index] = doors[index] with { State = doors[index].State == EDoorState.Open ? EDoorState.Closed : EDoorState.Open };
        return level with { Doors = doors };
    }

    private static LevelState PickUpRemedy(LevelState level, GridPosition position, PropState prop)
    {
        if (prop.Depleted || level.Robot.Count(prop.RemedyType) >= Balancing.Current.InventoryCapacityPerRemedy)
            return level;

        return level.SetProp(position, prop with { Depleted = true }) with { Robot = level.Robot.Add(prop.RemedyType, 1) };
    }

    private static LevelState RepairLeak(LevelState level, int leakIndex, LeakState leak)
    {
        var leaks = level.Leaks.ToArray();
        leaks[leakIndex] = leak with { Repaired = true };
        return level.SetUnderground(leak.UndergroundPosition, leak.Carrier, level.GetUnderground(leak.UndergroundPosition, leak.Carrier) with { State = EUndergroundState.Intact }) with { Leaks = leaks };
    }

    private static LevelState ApplyElementRemedy(LevelState level, LeakState leak)
    {
        var remedy = leak.Carrier switch {
            ECarrierType.Fuel => ERemedyType.FuelNeutralizer,
            ECarrierType.Coolant => ERemedyType.CoolantNeutralizer,
            ECarrierType.Electricity => ERemedyType.ElectricityNeutralizer,
            _ => throw new ArgumentOutOfRangeException(nameof(leak), leak.Carrier, "Unsupported leak carrier.")
        };

        if (level.Robot.Count(remedy) <= 0)
            return Refuse(level, "NO REMEDY");

        var surface = SurfaceCarrierMath.RemoveCarrier(level.GetSurface(leak.AccessPosition), leak.Carrier);
        return level.SetSurface(leak.AccessPosition, surface) with { Robot = level.Robot.Spend(remedy) };
    }

    private static LevelState CycleJunctionMode(LevelState level, GridPosition position, PropState prop)
    {
        var flow = JunctionFlowAnalyzer.Analyze(level).FirstOrDefault(junction => junction.Position == position);
        var outflowCount = flow?.OutgoingBranches.Count ?? 2;
        var ratios = Balancing.Current.JunctionRatios(outflowCount);
        if (ratios.Count == 0)
            return level;

        return level.SetProp(position, prop with { JunctionMode = (prop.JunctionMode + 1) % ratios.Count });
    }

    private static bool CanSpendAction(LevelState level)
    {
        return level.Global.LevelState is not (ELevelState.Lost or ELevelState.Won) && level.Global.ActionsRemaining > 0;
    }

    private static LevelState Refuse(LevelState level, string message)
    {
        return level with { Global = level.Global with { Status = message } };
    }
}