namespace ReactorMaintenance.Simulation;

internal static class ForecastSystem
{
    public static IReadOnlyList<Forecast> Forecast(LevelState level, Func<LevelState, LevelState> resolveTurn)
    {
        var forecasts = new List<Forecast>();
        var simulated = CopyForForecast(level);

        for (var turn = 0; turn <= Balancing.Current.ForecastHorizon; turn++)
        {
            AddForecasts(forecasts, simulated, turn);
            if (simulated.Global.LevelState is ELevelState.Lost or ELevelState.Ready or ELevelState.Won)
                break;

            if (turn < Balancing.Current.ForecastHorizon)
                simulated = resolveTurn(simulated);
        }

        return forecasts.DistinctBy(forecast => (forecast.Kind, forecast.Position, forecast.Message)).OrderBy(forecast => forecast.Turns).ThenBy(forecast => forecast.Message).ToArray();
    }

    private static LevelState CopyForForecast(LevelState level)
    {
        return level with {
            Terrain = level.Terrain.ToArray(),
            Fuel = level.Fuel.ToArray(),
            Coolant = level.Coolant.ToArray(),
            Electricity = level.Electricity.ToArray(),
            Surface = level.Surface.ToArray(),
            Props = level.Props.ToArray(),
            Forecasts = Array.Empty<Forecast>()
        };
    }

    private static void AddForecasts(List<Forecast> forecasts, LevelState level, int turn)
    {
        if (level.Global.LevelState == ELevelState.Lost)
            forecasts.Add(new(EForecastKind.TerminalLoss, level.Robot.Position, turn, level.Global.Status));

        if (level.Global.LevelState == ELevelState.Ready)
            forecasts.Add(new(EForecastKind.ReactorReady, null, turn, "REACTOR READY"));

        foreach (var position in LevelTraversal.AllPositions(level))
        {
            var prop = level.GetProp(position);
            if (prop.Type == EPropType.Consumer && prop.ServiceState == EConsumerServiceState.Starved)
                forecasts.Add(new(EForecastKind.ConsumerStarved, position, turn, $"{prop.Carrier} consumer starved"));

            var surface = level.GetSurface(position);
            if (SimulationBands.Fuel(surface.Fuel) == EBand.Critical || SimulationBands.Coolant(surface.Coolant) == EBand.Critical || SimulationBands.Electricity(surface.Electricity) == EBand.Critical || SimulationBands.Heat(surface.Heat) == EBand.Critical)
                forecasts.Add(new(EForecastKind.HazardGrowth, position, turn, "Critical hazard"));
        }

        foreach (var ruleEvent in level.RuleEvents.Where(ruleEvent => ruleEvent.Enabled && !string.IsNullOrWhiteSpace(ruleEvent.ForecastText) && ruleEvent.Predicates.All(predicate => RuleEventSystem.PredicateMatches(level, predicate))))
            forecasts.Add(new(EForecastKind.RuleEvent, null, turn, ruleEvent.ForecastText));
    }
}