class ShardStateObserver {

    private static final TimeValue MAX_WAIT_TIME_FOR_NEW_STATE = TimeValue.timeValueSeconds(30);
    private static final Logger LOGGER = LogManager.getLogger(ShardStateObserver.class);

    private final ClusterService clusterService;

    public ShardStateObserver(ClusterService clusterService) {
        this.clusterService = clusterService;
    }

    public CompletableFuture<ShardRouting> waitForActiveShard(ShardId shardId) {
        ClusterState state = clusterService.state();
        try {
            var routingTable = state.routingTable().shardRoutingTable(shardId);
            var primaryShardRouting = routingTable.primaryShard();
            if (primaryShardRouting.started()) {
                return CompletableFuture.completedFuture(primaryShardRouting);
            }
        } catch (IndexNotFoundException e) {
            return CompletableFuture.failedFuture(e);
        } catch (ShardNotFoundException ignored) {
            // Fall-through to use observer and wait for state update
        }
        var stateObserver = new ClusterStateObserver(
            state, clusterService, MAX_WAIT_TIME_FOR_NEW_STATE, LOGGER);
        var listener = new RetryIsShardActive(shardId);
        stateObserver.waitForNextChange(listener, newState -> shardStartedOrIndexDeleted(newState, shardId));
        return listener.result();
    }

    private static boolean shardStartedOrIndexDeleted(ClusterState state, ShardId shardId) {<FILL_FUNCTION_BODY>}

    private static class RetryIsShardActive implements ClusterStateObserver.Listener {

        private final ShardId shardId;

        private final CompletableFuture<ShardRouting> result = new CompletableFuture<>();

        RetryIsShardActive(ShardId shardId) {
            this.shardId = shardId;
        }

        @Override
        public void onNewClusterState(ClusterState state) {
            try {
                var routingTable = state.routingTable().shardRoutingTable(shardId);
                var primaryShardRouting = routingTable.primaryShard();
                if (primaryShardRouting.started()) {
                    result.complete(primaryShardRouting);
                }
            } catch (Throwable e) {
                result.completeExceptionally(e);
            }
        }

        @Override
        public void onClusterServiceClose() {
            result.completeExceptionally(new IllegalStateException(
                "ClusterService was closed while waiting for  shard=" + shardId + " to become active"));
        }

        @Override
        public void onTimeout(TimeValue timeout) {
            result.completeExceptionally(
                new TimeoutException("Timeout waiting for shard=" + shardId + " to become active"));
        }

        public CompletableFuture<ShardRouting> result() {
            return result;
        }
    }
}

        try {
            return state.routingTable().shardRoutingTable(shardId).primaryShard().started();
        } catch (ShardNotFoundException e) {
            return false;
        } catch (IndexNotFoundException e) {
            return true;
        }
    
<no_super_class>