namespace ReactorMaintenance.Simulation;

internal static class ReactorSystem
{
    public static LevelState Activate(LevelState level)
    {
        var reactorIndex = level.Reactors.ToList().FindIndex(reactor => reactor.ControlPosition == level.Robot.Position);
        if (reactorIndex < 0)
            return Refuse(level, "NO REACTOR CONTROL");

        var reactor = level.Reactors[reactorIndex];
        if (!reactor.Ready)
            return Refuse(level, "REACTOR NOT READY");

        var reactors = level.Reactors.ToArray();
        reactors[reactorIndex] = reactor with { Activated = true };
        return level with {
            Reactors = reactors,
            Global = level.Global with { LevelState = ELevelState.Won, Status = "REACTOR ONLINE" }
        };
    }

    public static LevelState DeriveState(LevelState level)
    {
        if (level.Global.LevelState is ELevelState.Lost or ELevelState.Won)
            return level;

        var reactors = level.Reactors.Select(reactor => reactor with { Ready = IsReady(level, reactor) }).ToArray();
        if (reactors.Any(reactor => reactor.Ready))
            return level with { Reactors = reactors, Global = level.Global with { LevelState = ELevelState.Ready, Status = "REACTOR READY" } };

        var maxHeat = level.Surface.Where((_, index) => level.Terrain[index] == ECellTerrain.Floor).Select(surface => surface.Heat).DefaultIfEmpty(0).Max();
        if (maxHeat >= Balancing.Current.TerminalHeat)
            return level with { Reactors = reactors, Global = level.Global with { LevelState = ELevelState.Lost, TerminalLoss = true, Status = "REACTOR HEAT TERMINAL" } };

        var hasCritical = level.Surface.Any(surface => SimulationBands.Fuel(surface.Fuel) == EBand.Critical || SimulationBands.Coolant(surface.Coolant) == EBand.Critical || SimulationBands.Electricity(surface.Electricity) == EBand.Critical || SimulationBands.Heat(surface.Heat) == EBand.Critical);
        var hasCaution = hasCritical || level.Props.Any(prop => prop.ServiceState is EConsumerServiceState.Starved or EConsumerServiceState.Disabled) || level.Leaks.Any(leak => !leak.Repaired);
        var state = hasCritical ? ELevelState.Critical :
            hasCaution ? ELevelState.Caution : ELevelState.Stable;
        return level with { Reactors = reactors, Global = level.Global with { LevelState = state, Status = state.ToString().ToUpperInvariant() } };
    }

    public static bool MatchesReady(LevelState level, RulePredicate predicate)
    {
        return level.Reactors.Any(reactor => MatchesId(reactor, predicate.ReactorId) && reactor.Ready) == predicate.BoolValue;
    }

    public static bool MatchesWon(LevelState level, RulePredicate predicate)
    {
        var won = predicate.ReactorId > 0
            ? level.Reactors.Any(reactor => reactor.ReactorId == predicate.ReactorId && reactor.Activated)
            : level.Global.LevelState == ELevelState.Won || level.Reactors.Any(reactor => reactor.Activated);
        return won == predicate.BoolValue;
    }

    private static bool IsReady(LevelState level, ReactorBinding reactor)
    {
        return HasProducingConsumer(level, reactor.FuelConsumerPosition, ECarrierType.Fuel)
            && HasProducingConsumer(level, reactor.CoolantConsumerPosition, ECarrierType.Coolant)
            && HasProducingConsumer(level, reactor.ElectricityConsumerPosition, ECarrierType.Electricity)
            && level.GetSurface(reactor.ControlPosition).Heat < Balancing.Current.TerminalHeat;
    }

    private static bool HasProducingConsumer(LevelState level, GridPosition position, ECarrierType carrier)
    {
        return level.InBounds(position) && level.GetProp(position) is { Type: EPropType.Consumer, Carrier: var consumerCarrier, ServiceState: EConsumerServiceState.Producing } && consumerCarrier == carrier;
    }

    private static bool MatchesId(ReactorBinding reactor, int reactorId)
    {
        return reactorId <= 0 || reactor.ReactorId == reactorId;
    }

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