#' Cache para regex compilados
#' @keywords internal
.acep_regex_cache <- new.env(parent = emptyenv())

#' Tope de entradas del caché de regex
#'
#' Cuando el caché alcanza este tamaño se vacía antes de agregar una nueva
#' entrada, evitando crecimiento de memoria sin límite en sesiones largas que
#' usan muchos diccionarios distintos. Un fallo de caché solo recompila el
#' patrón, por lo que la cota no afecta la correctitud de los conteos.
#' @keywords internal
.acep_regex_cache_max <- 1000L

#' Crear una clave de caché estable para diccionarios de conteo
#' @keywords internal
.acep_count_cache_key <- function(dic) {
  dic <- unique(dic)
  paste0(nchar(dic, type = "chars"), ":", dic, collapse = "\r")
}

#' Escapar metacaracteres de expresiones regulares en términos literales
#' @keywords internal
.acep_escape_regex <- function(x) {
  gsub("([][{}()*+?.\\\\^$|])", "\\\\\\1", x)
}

#' Crear patrón regex para diccionarios de conteo
#'
#' Escapa los metacaracteres de cada término (para tratarlos como texto literal,
#' evitando coincidencias accidentales o errores de regex) y convierte un espacio
#' inicial o final en un límite de palabra (\\b), de modo que " paro " coincida
#' solo con la palabra completa.
#' @keywords internal
.acep_count_pattern <- function(dic) {
  dic <- .acep_escape_regex(unique(dic))
  paste0(gsub("^ | $", "\\\\b", dic), collapse = "|")
}

#' @title Conteo de menciones de palabras de un diccionario
#' @description
#' Cuenta el número de veces que aparecen palabras de un diccionario en cada texto.
#' Los términos se tratan como texto literal: sus metacaracteres de expresiones
#' regulares se escapan automáticamente. De forma predeterminada también cuenta
#' coincidencias parciales (por ejemplo, "paro" coincide dentro de "paros"); para
#' exigir límites de palabra (word boundaries) y evitar coincidencias parciales,
#' rodeá cada término con espacios (por ejemplo, " paro "). Incluye un sistema de
#' caché que almacena los patrones regex compilados para acelerar ejecuciones
#' repetidas con el mismo diccionario. El caché se acota automáticamente
#' (máximo 1000 patrones) para evitar crecimiento de memoria sin límite en
#' sesiones largas; también podés vaciarlo manualmente con
#' `acep_clear_regex_cache()`.
#' @param texto vector de textos al que se le aplica la función de conteo.
#' @param dic vector de palabras del diccionario utilizado.
#' @param use_cache logical, usar caché de regex (default TRUE).
#' @return Vector con frecuencia de palabras del diccionario.
#' @keywords indicadores frecuencia tokens
#' @importFrom stringr str_count
#' @examples
#' df <- data.frame(texto = c("El SUTEBA fue al paro. Reclaman mejoras salariales.",
#' "El SOIP lleva adelante un plan de lucha con paros y piquetes."))
#' diccionario <- c("paro", "lucha", "piquetes")
#' df$detect <- acep_count(df$texto, diccionario)
#' df
#' @export
acep_count <- function(texto, dic, use_cache = TRUE) {
  validate_character(texto, "texto")
  validate_character(dic, "dic")

  # Crear hash del diccionario para caché
  if (use_cache) {
    dic_hash <- .acep_count_cache_key(dic)

    # Buscar en caché
    if (exists(dic_hash, envir = .acep_regex_cache)) {
      dicc <- get(dic_hash, envir = .acep_regex_cache)
    } else {
      # Compilar y guardar en caché (con tope de tamaño para acotar memoria)
      dicc <- .acep_count_pattern(dic)
      if (length(ls(envir = .acep_regex_cache)) >= .acep_regex_cache_max) {
        rm(list = ls(envir = .acep_regex_cache), envir = .acep_regex_cache)
      }
      assign(dic_hash, dicc, envir = .acep_regex_cache)
    }
  } else {
    dicc <- .acep_count_pattern(dic)
  }

  stringr::str_count(texto, dicc)
}

#' @title Limpiar caché de expresiones regulares
#' @description
#' Elimina todos los patrones regex almacenados en el caché interno de `acep_count()`.
#' Útil para liberar memoria cuando se han procesado muchos diccionarios diferentes
#' o cuando se quiere forzar la recompilación de patrones.
#'
#' @return Devuelve `NULL` invisiblemente.
#' @export
#' @examples
#' # Limpiar el caché
#' acep_clear_regex_cache()
acep_clear_regex_cache <- function() {
  rm(list = ls(envir = .acep_regex_cache), envir = .acep_regex_cache)
  invisible(NULL)
}

#' @title Consultar tamaño del caché de regex
#' @description
#' Devuelve el número de patrones regex almacenados actualmente en el caché
#' interno de `acep_count()`. Cada diccionario único genera una entrada en el caché.
#'
#' @return Número entero con la cantidad de patrones en caché.
#' @export
#' @examples
#' # Ver cuántos patrones hay en caché
#' acep_regex_cache_size()
acep_regex_cache_size <- function() {
  length(ls(envir = .acep_regex_cache))
}
