namespace ReactorMaintenance.Simulation;

public static class LevelStateExtensions
{
    public static bool InBounds(this LevelState level, GridPosition position)
    {
        return position.X >= 0 && position.Y >= 0 && position.X < level.Width && position.Y < level.Height;
    }

    public static int Index(this LevelState level, GridPosition position)
    {
        if (!level.InBounds(position))
            throw new ArgumentOutOfRangeException(nameof(position), $"Position {position.X},{position.Y} is outside {level.Width}x{level.Height}.");

        return (position.Y * level.Width) + position.X;
    }

    public static ECellTerrain GetTerrain(this LevelState level, GridPosition position)
    {
        return level.Terrain[level.Index(position)];
    }

    public static UndergroundCell GetUnderground(this LevelState level, GridPosition position, ECarrierType carrier)
    {
        return level.Layer(carrier)[level.Index(position)];
    }

    public static SurfaceState GetSurface(this LevelState level, GridPosition position)
    {
        return level.Surface[level.Index(position)];
    }

    public static PropState GetProp(this LevelState level, GridPosition position)
    {
        return level.Props[level.Index(position)];
    }

    public static bool IsFloor(this LevelState level, GridPosition position)
    {
        return level.InBounds(position) && level.GetTerrain(position) == ECellTerrain.Floor;
    }

    public static bool IsClosedDoorEdge(this LevelState level, GridPosition a, GridPosition b)
    {
        return level.Doors.Any(door => door.State == EDoorState.Closed && SameEdge(door.A, door.B, a, b));
    }

    public static LevelState SetTerrain(this LevelState level, GridPosition position, ECellTerrain terrain)
    {
        var next = level.Terrain.ToArray();
        next[level.Index(position)] = terrain;
        var updated = level with { Terrain = next };
        return terrain == ECellTerrain.Wall ? updated.ClearFloorOnlyState(position) : updated;
    }

    public static LevelState SetUnderground(this LevelState level, GridPosition position, ECarrierType carrier, UndergroundCell cell)
    {
        var next = level.Layer(carrier).ToArray();
        next[level.Index(position)] = cell;
        return carrier switch {
            ECarrierType.Fuel => level with { Fuel = next },
            ECarrierType.Coolant => level with { Coolant = next },
            ECarrierType.Electricity => level with { Electricity = next },
            _ => throw new ArgumentOutOfRangeException(nameof(carrier), carrier, "Unsupported carrier.")
        };
    }

    public static LevelState SetSurface(this LevelState level, GridPosition position, SurfaceState surface)
    {
        var next = level.Surface.ToArray();
        next[level.Index(position)] = surface.Clamp();
        return level with { Surface = next };
    }

    public static LevelState SetProp(this LevelState level, GridPosition position, PropState prop)
    {
        var next = level.Props.ToArray();
        next[level.Index(position)] = prop;
        return level with { Props = next };
    }

    public static LevelState WithRuntimeArrays(this LevelState level, UndergroundCell[] fuel, UndergroundCell[] coolant, UndergroundCell[] electricity, SurfaceState[] surface, PropState[] props)
    {
        return level with {
            Fuel = fuel,
            Coolant = coolant,
            Electricity = electricity,
            Surface = surface,
            Props = props
        };
    }

    public static IReadOnlyList<UndergroundCell> Layer(this LevelState level, ECarrierType carrier)
    {
        return carrier switch {
            ECarrierType.Fuel => level.Fuel,
            ECarrierType.Coolant => level.Coolant,
            ECarrierType.Electricity => level.Electricity,
            _ => throw new ArgumentOutOfRangeException(nameof(carrier), carrier, "Unsupported carrier.")
        };
    }

    private static LevelState ClearFloorOnlyState(this LevelState level, GridPosition position)
    {
        return level.SetSurface(position, new())
                    .SetProp(position, new())
                    .SetUnderground(position, ECarrierType.Fuel, new())
                    .SetUnderground(position, ECarrierType.Coolant, new())
                    .SetUnderground(position, ECarrierType.Electricity, new());
    }

    private static bool SameEdge(GridPosition edgeA, GridPosition edgeB, GridPosition a, GridPosition b)
    {
        return (edgeA == a && edgeB == b) || (edgeA == b && edgeB == a);
    }
}