namespace ReactorMaintenance.Simulation;

internal static class RuleEventSystem
{
    public static LevelState Apply(LevelState level, ERuleEventPhase phase)
    {
        var next = level;
        var events = level.RuleEvents.Select((ruleEvent, index) => (Event: ruleEvent, Index: index)).Where(item => item.Event.Enabled && item.Event.Phase == phase && (item.Event.Repeat || !item.Event.Triggered)).OrderBy(item => item.Event.Priority).ToArray();
        var ruleEvents = next.RuleEvents.ToArray();

        foreach (var item in events)
        {
            if (!item.Event.Predicates.All(predicate => PredicateMatches(next, predicate)))
                continue;

            foreach (var effect in item.Event.Effects)
                next = ApplyEffect(next, effect);

            ruleEvents[item.Index] = item.Event with { Triggered = true };
        }

        return next with { RuleEvents = ruleEvents };
    }

    public static bool PredicateMatches(LevelState level, RulePredicate predicate)
    {
        return predicate.Kind switch {
            ERulePredicateKind.TurnAtLeast => level.Global.Turn >= predicate.Turn,
            ERulePredicateKind.LevelStateIs => level.Global.LevelState == predicate.LevelState,
            ERulePredicateKind.ReactorReadyIs => ReactorSystem.MatchesReady(level, predicate),
            ERulePredicateKind.ReactorLostIs => level.Global.LevelState == ELevelState.Lost == predicate.BoolValue,
            ERulePredicateKind.ReactorWonIs => ReactorSystem.MatchesWon(level, predicate),
            ERulePredicateKind.PropStateAt => level.InBounds(predicate.Position) && level.GetProp(predicate.Position).SwitchState == predicate.PropSwitchState,
            ERulePredicateKind.ConsumerStateAt => level.InBounds(predicate.Position) && level.GetProp(predicate.Position).ServiceState == predicate.ConsumerServiceState,
            ERulePredicateKind.NetworkBandAt => level.InBounds(predicate.Position) && SimulationBands.NetworkBand(level.GetUnderground(predicate.Position, predicate.Carrier), predicate.Carrier, predicate.NetworkValue) >= predicate.Band,
            ERulePredicateKind.SurfaceBandAt => level.InBounds(predicate.Position) && SimulationBands.SurfaceBand(level.GetSurface(predicate.Position), predicate.Carrier) >= predicate.Band,
            ERulePredicateKind.RobotAt => level.Robot.Position == predicate.Position,
            ERulePredicateKind.RobotInventoryAtLeast => level.Robot.Count(predicate.Remedy) >= predicate.InventoryCount,
            ERulePredicateKind.AllSeeingEyeUnlocked => level.Global.AllSeeingEyeUnlocked == predicate.BoolValue,
            _ => false
        };
    }

    private static LevelState ApplyEffect(LevelState level, RuleEffect effect)
    {
        return effect.Kind switch {
            ERuleEffectKind.StartLeak => StartLeak(level, effect),
            ERuleEffectKind.WorsenLeak => level.SetUnderground(effect.Position, effect.Carrier, level.GetUnderground(effect.Position, effect.Carrier) with { State = EUndergroundState.Leaking }),
            ERuleEffectKind.RepairNetworkCell => level.SetUnderground(effect.Position, effect.Carrier, level.GetUnderground(effect.Position, effect.Carrier) with { State = EUndergroundState.Intact }),
            ERuleEffectKind.DisableNetworkCell => level.SetUnderground(effect.Position, effect.Carrier, new()),
            ERuleEffectKind.SetPropEnabled => level.SetProp(effect.Position, level.GetProp(effect.Position) with { SwitchState = effect.PropSwitchState }),
            ERuleEffectKind.AddSurfaceHazard => level.SetSurface(effect.Position, SurfaceCarrierMath.AddCarrier(level.GetSurface(effect.Position), effect.Carrier, effect.Amount)),
            ERuleEffectKind.RemoveSurfaceHazard => level.SetSurface(effect.Position, SurfaceCarrierMath.AddCarrier(level.GetSurface(effect.Position), effect.Carrier, -effect.Amount)),
            ERuleEffectKind.AddHeat => level.SetSurface(effect.Position, level.GetSurface(effect.Position) with { Heat = level.GetSurface(effect.Position).Heat + effect.Amount }),
            ERuleEffectKind.RemoveHeat => level.SetSurface(effect.Position, level.GetSurface(effect.Position) with { Heat = level.GetSurface(effect.Position).Heat - effect.Amount }),
            ERuleEffectKind.AddInventory => level with { Robot = level.Robot.Add(effect.Remedy, (int)effect.Amount) },
            ERuleEffectKind.RemoveInventory => level with { Robot = level.Robot.Add(effect.Remedy, -(int)effect.Amount) },
            ERuleEffectKind.MarkTerminalLoss => level with { Global = level.Global with { LevelState = ELevelState.Lost, TerminalLoss = true, Status = string.IsNullOrWhiteSpace(effect.Message) ? "TERMINAL FAILURE" : effect.Message } },
            ERuleEffectKind.EmitWarning => level with { Global = level.Global with { Warnings = [.. level.Global.Warnings, effect.Message] } },
            _ => level
        };
    }

    private static LevelState StartLeak(LevelState level, RuleEffect effect)
    {
        var leak = new LeakState {
            Carrier = effect.Carrier,
            UndergroundPosition = effect.Position,
            AccessPosition = effect.AccessPosition ?? effect.Position
        };
        return level.SetUnderground(effect.Position, effect.Carrier, level.GetUnderground(effect.Position, effect.Carrier) with { State = EUndergroundState.Leaking }) with { Leaks = [.. level.Leaks, leak] };
    }
}