ngram_frequencies <- function(x, min_freq = c(6, 4, 2), mapping = NULL) {

  # --- Eingabe in Keilschrift konvertieren (falls noetig) ---
  # Pruefen ob bereits Keilschrift enthalten ist (U+12000..U+1254F)
  has_cuneiform <- any(grepl("[\U00012000-\U0001254F]", x, perl = TRUE))
  if (!has_cuneiform && length(x) > 0) {
    x <- as.cuneiform(x, mapping = mapping)
  }

  x <- str_replace_all(x, " ", "")

  # --- Vorverarbeitung ---
  # Kommentarzeilen (beginnen mit #) und Leerzeilen entfernen
  x <- x[!grepl("^\\s*#", x)]
  x <- x[nchar(trimws(x)) > 0]

  # Zeilennummern entfernen: "42)\t" oder "42) " am Zeilenanfang
  x <- sub("^\\s*\\d+\\)\\s*", "", x)

  # --- Tokenisierung ---
  # Jedes Keilschriftzeichen (U+12000..U+1254F) ist ein Token.
  # Jede Zeichenfolge in \u27E8...\u27E9 ist ein Token.
  # Alles andere wird ignoriert.
  tokenize_line <- function(line) {
    chars  <- strsplit(line, "")[[1]]
    n_ch   <- length(chars)
    tokens <- character(0)
    i      <- 1L

    while (i <= n_ch) {
      cp <- utf8ToInt(chars[i])

      if (cp == 0x27E8L) {
        # \u27E8 gefunden - alles bis \u27E9 sammeln
        bracket <- chars[i]
        i <- i + 1L
        while (i <= n_ch && utf8ToInt(chars[i]) != 0x27E9L) {
          bracket <- paste0(bracket, chars[i])
          i <- i + 1L
        }
        if (i <= n_ch) {
          bracket <- paste0(bracket, chars[i])  # \u27E9 anhaengen
          i <- i + 1L
        }
        tokens <- c(tokens, bracket)

      } else if (cp >= 0x12000L && cp <= 0x1254FL) {
        tokens <- c(tokens, chars[i])
        i <- i + 1L

      } else {
        i <- i + 1L
      }
    }
    tokens
  }

  lines_tok <- lapply(x, tokenize_line)
  lines_tok <- lines_tok[lengths(lines_tok) > 0L]

  if (length(lines_tok) == 0L) {
    return(data.frame(frequency   = integer(0),
                      length      = integer(0),
                      combination = character(0),
                      stringsAsFactors = FALSE))
  }

  # --- max_n automatisch bestimmen ---
  max_n <- max(lengths(lines_tok))

  # --- min_freq aufbereiten ---
  if (is.null(min_freq)) {
    min_freq <- c(6L, 4L, 2L)
  }
  min_freq <- as.integer(min_freq)

  # --- C-Kernschleife: N-Gramm-Extraktion, Zaehlung und Maskierung ---
  result <- .Call(C_ngram_core, lines_tok, as.integer(max_n), min_freq)

  # --- Ergebnis zusammenbauen ---
  out <- data.frame(
    frequency   = result$frequency,
    length      = result$length,
    combination = result$combination,
    stringsAsFactors = FALSE
  )

  if (nrow(out) == 0L) {
    return(out)
  }

  out <- out[order(-out$length, -out$frequency), ]
  rownames(out) <- NULL

  # Spaltenreihenfolge: frequency, length, combination
  out[, c("frequency", "length", "combination")]
}
