specc_embedding <- function(X, n_components, sigma) {
  needed <- c("kernlab", "igraph", "RSpectra")
  missing <- needed[!vapply(needed, requireNamespace, logical(1), quietly = TRUE)]
  if (length(missing) > 0) {
    stop("Install the following packages first: ", paste(missing, collapse = ", "))
  }

  X <- as.matrix(X)
  if (n_components < 2 || n_components >= nrow(X)) {
    stop("n_components must be at least 2 and smaller than the number of rows in X")
  }

  affinity <- as.matrix(kernlab::kernelMatrix(
    kernlab::rbfdot(sigma = sigma), X
  ))
  diag(affinity) <- 0
  affinity <- (affinity + t(affinity)) / 2

  graph <- igraph::graph_from_adjacency_matrix(
    affinity,
    mode = "undirected",
    weighted = TRUE,
    diag = FALSE
  )
  laplacian <- igraph::laplacian_matrix(
    graph,
    weights = igraph::E(graph)$weight,
    normalization = "symmetric",
    sparse = TRUE
  )

  eig <- RSpectra::eigs_sym(laplacian, k = n_components, which = "SA")
  idx <- order(eig$values)
  coordinates <- eig$vectors[, idx, drop = FALSE]

  row_length <- sqrt(rowSums(coordinates^2))
  coordinates <- sweep(coordinates, 1, row_length, "/")
  colnames(coordinates) <- paste0("spectral_", seq_len(n_components))
  attr(coordinates, "eigenvalues") <- eig$values[idx]
  coordinates
}
