namespace ReactorMaintenance.Simulation;

public enum ECellTerrain
{
    Floor,
    Wall
}

public enum ECellProp
{
    None,
    Reactor,
    CoolingPump,
    Generator,
    PressureRegulator,
    DiagnosticTerminal,
    ControlTerminal
}

public enum EPipeMedium
{
    None,
    Pressure,
    Coolant,
    Fuel
}

public enum EFailureKind
{
    PipeBurst,
    Ignition,
    Meltdown,
    StabilityCollapse,
    ReactorReady
}

public sealed record GridPosition(int X, int Y)
{
    public IEnumerable<GridPosition> Neighbors()
    {
        yield return new(X - Balancing.Current.NeighborDistance, Y);
        yield return new(X + Balancing.Current.NeighborDistance, Y);
        yield return new(X, Y - Balancing.Current.NeighborDistance);
        yield return new(X, Y + Balancing.Current.NeighborDistance);
    }
}

public sealed record HazardState
{
    public HazardState Clamp()
    {
        return this with {
            Heat = Rules.Clamp(Heat),
            Smoke = Rules.Clamp(Smoke),
            FuelVapor = Rules.Clamp(FuelVapor),
            LiquidFuel = Rules.Clamp(LiquidFuel),
            CoolantPooling = Rules.Clamp(CoolantPooling),
            ElectricalCharge = Rules.Clamp(ElectricalCharge),
            Stability = Rules.Clamp(Stability)
        };
    }

    public int Heat { get; init; }
    public int Smoke { get; init; }
    public int FuelVapor { get; init; }
    public int LiquidFuel { get; init; }
    public int CoolantPooling { get; init; }
    public int ElectricalCharge { get; init; }
    public int Stability { get; init; } = Balancing.Current.DefaultHazardStability;
    public bool Fire { get; init; }
}

public sealed record CellState
{
    public ECellTerrain Terrain { get; init; } = ECellTerrain.Floor;
    public ECellProp Prop { get; init; }
    public EPipeMedium Pipe { get; init; }
    public int Flow { get; init; }
    public int Pressure { get; init; }
    public int Integrity { get; init; } = Balancing.Current.DefaultCellIntegrity;
    public int LeakRate { get; init; }
    public bool PipeOpen { get; init; } = true;
    public bool Powered { get; init; }
    public bool DoorLocked { get; init; }
    public HazardState Hazards { get; init; } = new();
    public bool IsWalkable => Terrain != ECellTerrain.Wall;
    public bool HasPipe => Pipe != EPipeMedium.None;
}

public sealed record GlobalState
{
    public int Turn { get; init; }
    public int ActionsPerTurn { get; init; } = Balancing.Current.DefaultActionsPerTurn;
    public int CoreHeat { get; init; } = Balancing.Current.DefaultCoreHeat;
    public int FacilityStability { get; init; } = Balancing.Current.DefaultFacilityStability;
    public int Power { get; init; } = Balancing.Current.DefaultPower;
    public int Cooling { get; init; } = Balancing.Current.DefaultCooling;
    public bool ReactorActivated { get; init; }
    public bool Lost { get; init; }
    public string Status { get; init; } = "STABILIZE SYSTEMS";
}

public sealed record Forecast(EFailureKind Kind, GridPosition? Position, int Turns, string Message);

public sealed record LevelState
{
    public static LevelState Create(string name, int width, int height)
    {
        if (width < Balancing.Current.MinimumLevelSize || height < Balancing.Current.MinimumLevelSize)
            throw new ArgumentOutOfRangeException(nameof(width), $"Levels must be at least {Balancing.Current.MinimumLevelSize}x{Balancing.Current.MinimumLevelSize}.");

        var cells = CreateCells(width, height);
        for (var y = Balancing.Current.FirstGridCoordinate; y < height; y++)
        {
            for (var x = Balancing.Current.FirstGridCoordinate; x < width; x++)
            {
                if (x == Balancing.Current.FirstGridCoordinate || y == Balancing.Current.FirstGridCoordinate || x == width - Balancing.Current.NeighborDistance || y == height - Balancing.Current.NeighborDistance)
                    cells[y * width + x] = cells[y * width + x] with { Terrain = ECellTerrain.Wall };
            }
        }

        return new() {
            Name = name,
            Width = width,
            Height = height,
            Cells = cells,
            Robot = new(Balancing.Current.DefaultRobotCoordinate, Balancing.Current.DefaultRobotCoordinate)
        };
    }

    public CellState GetCell(GridPosition position)
    {
        EnsureInBounds(position);
        return Cells[Index(position)];
    }

    public LevelState SetCell(GridPosition position, CellState cell)
    {
        EnsureInBounds(position);
        var cells = Cells.ToArray();
        cells[Index(position)] = cell;
        return this with { Cells = cells };
    }

    public bool InBounds(GridPosition position)
    {
        return position.X >= Balancing.Current.FirstGridCoordinate && position.Y >= Balancing.Current.FirstGridCoordinate && position.X < Width && position.Y < Height;
    }

    public int Index(GridPosition position)
    {
        return position.Y * Width + position.X;
    }

    private void EnsureInBounds(GridPosition position)
    {
        if (!InBounds(position))
            throw new ArgumentOutOfRangeException(nameof(position), $"Position {position.X},{position.Y} is outside {Width}x{Height}.");
    }

    private static CellState[] CreateCells(int width, int height)
    {
        return Enumerable.Range(Balancing.Current.FirstGridCoordinate, width * height).Select(_ => new CellState()).ToArray();
    }

    public string Name { get; init; } = "New Reactor";
    public int Width { get; init; } = Balancing.Current.DefaultLevelWidth;
    public int Height { get; init; } = Balancing.Current.DefaultLevelHeight;
    public CellState[] Cells { get; init; } = CreateCells(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight);
    public GridPosition Robot { get; init; } = new(Balancing.Current.DefaultRobotCoordinate, Balancing.Current.DefaultRobotCoordinate);
    public GlobalState Global { get; init; } = new();
    public IReadOnlyList<Forecast> Forecasts { get; init; } = Array.Empty<Forecast>();
}

internal static class Rules
{
    public static int Clamp(int value)
    {
        return Math.Clamp(value, Balancing.Current.MinHazardValue, Balancing.Current.MaxHazardValue);
    }
}