docs: private void nnChainCore(MatrixParadigm mat, DBIDArrayMIter prots, DistanceQuery<O> dq, PointerHierarchyRepresentationBuilder builder, Int2ObjectOpenHashMap<ModifiableDBIDs> clusters) {
      final DBIDArrayIter ix = mat.ix;
      final double[] distances = mat.matrix;
      final int size = mat.size;
      // The maximum chain size = number of ids + 1
      IntegerArray chain = new IntegerArray(size + 1);
  
      FiniteProgress progress = LOG.isVerbose() ? new FiniteProgress("Running MiniMax-NNChain", size - 1, LOG) : null;
      for(int k = 1, end = size; k < size; k++) {
        int a = -1, b = -1;
        if(chain.size() <= 3) {
          // Accessing two arbitrary not yet merged elements could be optimized to
          // work in O(1) like in Müllner;
          // however this usually does not have a huge impact (empirically just
          // about 1/5000 of total performance)
          a = NNChain.findUnlinked(0, end, ix, builder);
          b = NNChain.findUnlinked(a + 1, end, ix, builder);
          chain.clear();
          chain.add(a);
        }
        else {
          // Chain is expected to look like (.... a, b, c, b) with b and c merged.
          int lastIndex = chain.size;
          int c = chain.get(lastIndex - 2);
          b = chain.get(lastIndex - 3);
          a = chain.get(lastIndex - 4);
          // Ensure we had a loop at the end:
          assert (chain.get(lastIndex - 1) == c || chain.get(lastIndex - 1) == b);
          // if c < b, then we merged b -> c, otherwise c -> b
          b = c < b ? c : b;
          // Cut the tail:
          chain.size -= 3;
        }
        // For ties, always prefer the second-last element b:
        double minDist = mat.get(a, b);
        do {
          int c = b;
          final int ta = MatrixParadigm.triangleSize(a);
          for(int i = 0; i < a; i++) {
            if(i != b && !builder.isLinked(ix.seek(i))) {
              double dist = distances[ta + i];
              if(dist < minDist) {
                minDist = dist;
                c = i;
              }
            }
          }
          for(int i = a + 1; i < size; i++) {
            if(i != b && !builder.isLinked(ix.seek(i))) {
              double dist = distances[MatrixParadigm.triangleSize(i) + a];
              if(dist < minDist) {
                minDist = dist;
                c = i;
              }
            }
          }
  
          b = a;
          a = c;
  
          chain.add(a);
        }
        while(chain.size() < 3 || a != chain.get(chain.size - 1 - 2));
  
        // We always merge the larger into the smaller index:
        if(a < b) {
          int tmp = a;
          a = b;
          b = tmp;
        }
        assert (minDist == mat.get(a, b));
        assert (b < a);
        MiniMax.merge(size, mat, prots, builder, clusters, dq, a, b);
        end = AGNES.shrinkActiveSet(ix, builder, end, a); // Shrink working set
        LOG.incrementProcessed(progress);
      }
      LOG.ensureCompleted(progress);
    }