#' @title Convertir Tweets o Usuarios a Data Frame
#' @description Convierte una lista de tweets o usuarios a data.frame usando purrr::map_dfr
#' @param x Lista de tweets o usuarios
#' @return data.frame o tibble
#' @export
#' @examples
#' \donttest{
#' tweets <- list(
#'   list(
#'     id = "1", date = as.POSIXct("2026-01-01"), text = "Hello R",
#'     username = "rstats", user_displayname = "R Stats", user_id = "10",
#'     reply_count = 0, retweet_count = 1, like_count = 2, quote_count = 0,
#'     views_count = 100, lang = "en", url = "https://example.com/1",
#'     user_followers = 1000, user_verified = FALSE
#'   )
#' )
#' class(tweets) <- c("twscraper_tweets", "list")
#' df <- to_dataframe(tweets)
#'
#' users <- list(list(
#'   id = "10", username = "rstats", displayname = "R Stats",
#'   description = "Example user", followers_count = 1000,
#'   following_count = 50, tweets_count = 200, verified = FALSE,
#'   created = "2026-01-01", location = "", url = "",
#'   profile_image_url = ""
#' ))
#' class(users) <- c("twscraper_users", "list")
#' users_df <- to_dataframe(users)
#'
#' users[[1]]$source_tweet_id <- "1"
#' retweeters_df <- to_dataframe(users)  # incluye source_tweet_id
#' }
to_dataframe <- function(x) {
  # Check if it's a single user object (has 12 elements with class twscraper_user)
  if (inherits(x, "twscraper_user")) {
    # Single user object with 12 fields - convert with as_tibble
    if (requireNamespace("tibble", quietly = TRUE)) {
      return(tibble::as_tibble(x))
    } else {
      return(as.data.frame(x, stringsAsFactors = FALSE))
    }
  }

  # Check if it's a single tweet object
  if (inherits(x, "twscraper_tweet")) {
    # Single tweet object - convert with as_tibble
    if (requireNamespace("tibble", quietly = TRUE)) {
      return(tibble::as_tibble(x))
    } else {
      return(as.data.frame(x, stringsAsFactors = FALSE))
    }
  }

  if (length(x) == 0) {
    cli::cli_alert_warning("No hay elementos para convertir")
    if (requireNamespace("tibble", quietly = TRUE)) {
      return(tibble::tibble())
    } else {
      return(data.frame())
    }
  }

  # Check if single element list - use as_tibble directly
  if (length(x) == 1) {
    if (requireNamespace("tibble", quietly = TRUE)) {
      return(tibble::as_tibble(x[[1]]))
    } else {
      return(as.data.frame(x[[1]], stringsAsFactors = FALSE))
    }
  }

  # Use purrr::map_dfr if available with as_tibble, otherwise fallback to base R
  if (requireNamespace("purrr", quietly = TRUE) && requireNamespace("tibble", quietly = TRUE)) {
    df <- purrr::map_dfr(x, ~tibble::as_tibble(.x))
  } else {
    # Fallback to base R - detect if users or tweets
    first_elem <- x[[1]]
    is_users <- !is.null(first_elem$displayname) && is.null(first_elem$text)

    if (is_users) {
      df <- data.frame(
        user_id = sapply(x, function(u) u$id %||% NA_character_),
        username = sapply(x, function(u) u$username %||% NA_character_),
        displayname = sapply(x, function(u) u$displayname %||% NA_character_),
        description = sapply(x, function(u) u$description %||% NA_character_),
        followers_count = sapply(x, function(u) as.numeric(u$followers_count %||% 0)),
        following_count = sapply(x, function(u) as.numeric(u$following_count %||% 0)),
        tweets_count = sapply(x, function(u) as.numeric(u$tweets_count %||% 0)),
        verified = sapply(x, function(u) isTRUE(u$verified)),
        created = sapply(x, function(u) u$created %||% NA_character_),
        location = sapply(x, function(u) u$location %||% NA_character_),
        url = sapply(x, function(u) u$url %||% NA_character_),
        profile_image_url = sapply(x, function(u) u$profile_image_url %||% NA_character_),
        stringsAsFactors = FALSE
      )

      has_source_tweet_id <- any(vapply(x, function(u) !is.null(u$source_tweet_id), logical(1)))
      if (has_source_tweet_id) {
        df <- data.frame(
          source_tweet_id = sapply(x, function(u) u$source_tweet_id %||% NA_character_),
          df,
          stringsAsFactors = FALSE
        )
      }
    } else {
      df <- data.frame(
        tweet_id = sapply(x, function(t) t$id %||% NA_character_),
        date = as.POSIXct(sapply(x, function(t) as.character(t$date %||% NA))),
        username = sapply(x, function(t) t$username %||% NA_character_),
        user_displayname = sapply(x, function(t) t$user_displayname %||% NA_character_),
        user_id = sapply(x, function(t) t$user_id %||% NA_character_),
        text = sapply(x, function(t) t$text %||% NA_character_),
        reply_count = as.numeric(sapply(x, function(t) t$reply_count %||% 0)),
        retweet_count = as.numeric(sapply(x, function(t) t$retweet_count %||% 0)),
        like_count = as.numeric(sapply(x, function(t) t$like_count %||% 0)),
        quote_count = as.numeric(sapply(x, function(t) t$quote_count %||% 0)),
        views_count = as.numeric(sapply(x, function(t) t$views_count %||% 0)),
        lang = sapply(x, function(t) t$lang %||% NA_character_),
        url = sapply(x, function(t) t$url %||% NA_character_),
        user_followers = as.numeric(sapply(x, function(t) t$user_followers %||% 0)),
        user_verified = as.logical(sapply(x, function(t) t$user_verified %||% FALSE)),
        stringsAsFactors = FALSE
      )
    }
  }

  return(df)
}

#' @title Guardar Tweets como CSV
#' @description Guarda tweets en formato CSV
#' @param tweets Lista de tweets
#' @param file Nombre del archivo
#' @return Invisibly returns the output file path. The CSV contains one row per tweet/user-like item after conversion with `to_dataframe()`.
#' @export
#' @examples
#' \donttest{
#' tweets <- list(list(id = "1", text = "Hello R", username = "rstats"))
#' save_csv(tweets, tempfile(fileext = ".csv"))
#' }
save_csv <- function(tweets, file) {
  df <- to_dataframe(tweets)
  utils::write.csv(df, file, row.names = FALSE, fileEncoding = "UTF-8")
  cli::cli_alert_success("Guardado en: {file}")
  cli::cli_text("  {nrow(df)} tweets")
  return(invisible(file))
}

#' @title Guardar Tweets como JSON
#' @description Guarda tweets en formato JSON
#' @param tweets Lista de tweets
#' @param file Nombre del archivo
#' @return Invisibly returns the output file path. The JSON file contains the list structure supplied in `tweets`, written with `jsonlite::write_json()`.
#' @export
#' @examples
#' \donttest{
#' tweets <- list(list(id = "1", text = "Hello R", username = "rstats"))
#' save_json(tweets, tempfile(fileext = ".json"))
#' }
save_json <- function(tweets, file) {
  if (!requireNamespace("jsonlite", quietly = TRUE)) {
    stop("El paquete 'jsonlite' es necesario y debe estar disponible antes de usar save_json().", call. = FALSE)
  }

  jsonlite::write_json(tweets, file, pretty = TRUE, auto_unbox = TRUE)
  cli::cli_alert_success("Guardado en: {file}")
  cli::cli_text("  {length(tweets)} tweets")
  return(invisible(file))
}

#' @title Metodo Print para Tweets
#' @description Imprime resumen de tweets
#' @param x Lista de tweets
#' @param ... Argumentos adicionales
#' @return Invisibly returns `x`, a `twscraper_tweets` list. Each element represents one tweet with fields such as `id`, `date`, `text`, `username`, engagement counts, language, and URL.
#' @export
print.twscraper_tweets <- function(x, ...) {
  cli::cli_h2("Tweets de twscrapeR")
  cli::cli_text("")
  cli::cli_alert_info("Total: {length(x)} tweets")

  if (length(x) > 0) {
    cli::cli_text("")
    cli::cli_text("{.strong Primeros 3 tweets:}")
    cli::cli_text("")

    for (i in 1:min(3, length(x))) {
      tw <- x[[i]]
      cli::cli_text("{i}. @{tw$username} ({tw$user_displayname})")
      cli::cli_text("   {substr(tw$text, 1, 70)}...")
      cli::cli_text("   \u2764 {tw$like_count} | \U0001f501 {tw$retweet_count} | \U0001f441 {tw$views_count}")
      cli::cli_text("")
    }

    if (length(x) > 3) {
      cli::cli_text("   ... y {length(x) - 3} tweets m\u00e1s")
    }

    cli::cli_text("")
    cli::cli_text("{.emph Usa to_dataframe() para convertir a data.frame}")
  }

  invisible(x)
}

#' @title Metodo Print para Usuario
#' @description Imprime informacion de usuario
#' @param x Informacion de usuario
#' @param ... Argumentos adicionales
#' @return Invisibly returns `x`, a `twscraper_user` list with fields such as `id`, `username`, `displayname`, `description`, follower/following counts, verification status, location, URL, and profile image URL.
#' @export
print.twscraper_user <- function(x, ...) {
  cli::cli_h2("Usuario de Twitter")
  cli::cli_text("")

  cli::cli_dl(c(
    "Username" = paste0("@", x$username),
    "Nombre" = x$displayname,
    "ID" = x$id,
    "Descripci\u00f3n" = substr(x$description, 1, 100),
    "Seguidores" = format(x$followers_count, big.mark = ","),
    "Siguiendo" = format(x$following_count, big.mark = ","),
    "Tweets" = format(x$tweets_count, big.mark = ","),
    "Verificado" = if (x$verified) "S\u00ed" else "No",
    "Ubicaci\u00f3n" = x$location,
    "URL" = x$url
  ))

  invisible(x)
}

#' @title Metodo Print para Lista de Usuarios
#' @description Imprime resumen de usuarios
#' @param x Lista de usuarios
#' @param ... Argumentos adicionales
#' @return Invisibly returns `x`, a `twscraper_users` list. Each element represents one user with profile metadata and account statistics.
#' @export
print.twscraper_users <- function(x, ...) {
  cli::cli_h2("Usuarios de twscrapeR")
  cli::cli_text("")
  cli::cli_alert_info("Total: {length(x)} usuarios")

  if (length(x) > 0) {
    cli::cli_text("")
    cli::cli_text("{.strong Primeros 5 usuarios:}")
    cli::cli_text("")

    for (i in 1:min(5, length(x))) {
      user <- x[[i]]
      cli::cli_text("{i}. @{user$username} ({user$displayname})")

      # Descripcion truncada
      desc <- user$description %||% ""
      if (nchar(desc) > 60) {
        desc <- paste0(substr(desc, 1, 60), "...")
      }
      if (nchar(desc) > 0) {
        cli::cli_text("   {desc}")
      }

      cli::cli_text("   \U0001f465 {format(user$followers_count, big.mark = ',')} seguidores | \U0001f4dd {format(user$tweets_count, big.mark = ',')} tweets")
      cli::cli_text("")
    }

    if (length(x) > 5) {
      cli::cli_text("   ... y {length(x) - 5} usuarios m\u00e1s")
    }

    cli::cli_text("")
    cli::cli_text("{.emph Usa to_dataframe() para convertir a data.frame}")
  }

  invisible(x)
}

#' @title Metodo Print para Tweet Individual
#' @description Imprime informacion de un tweet individual
#' @param x Informacion de un tweet
#' @param ... Argumentos adicionales
#' @return Invisibly returns `x`, a `twscraper_tweet` list with tweet metadata, author fields, engagement counts, language, and URL.
#' @export
print.twscraper_tweet <- function(x, ...) {
  cli::cli_h2("Tweet de Twitter")
  cli::cli_text("")

  cli::cli_text("{.strong Autor:} @{x$username} ({x$user_displayname})")
  cli::cli_text("{.strong ID:} {x$id}")
  cli::cli_text("{.strong Fecha:} {format(x$date, '%Y-%m-%d %H:%M:%S')}")
  cli::cli_text("")

  # Mostrar el texto completo
  cli::cli_text("{.strong Texto:}")
  cli::cli_text("  {x$text}")
  cli::cli_text("")

  # Estadisticas
  cli::cli_text("{.strong Estad\u00edsticas:}")
  cli::cli_text("  \u2764  Likes: {format(x$like_count, big.mark = ',')}")
  cli::cli_text("  \U0001f501 Retweets: {format(x$retweet_count, big.mark = ',')}")
  cli::cli_text("  \U0001f4ac Respuestas: {format(x$reply_count, big.mark = ',')}")
  cli::cli_text("  \U0001f516 Quotes: {format(x$quote_count, big.mark = ',')}")
  cli::cli_text("  \U0001f441  Vistas: {format(x$views_count, big.mark = ',')}")
  cli::cli_text("")

  cli::cli_text("{.strong Idioma:} {x$lang}")
  cli::cli_text("{.strong URL:} {x$url}")
  cli::cli_text("")
  cli::cli_text("{.emph Usa to_dataframe() para convertir a data.frame}")

  invisible(x)
}

#' @title Convertir Usuario a Data Frame
#' @description Convierte un usuario individual a data.frame de una fila
#' @param x Usuario de get_user()
#' @param ... Argumentos adicionales
#' @return data.frame
#' @export
as.data.frame.twscraper_user <- function(x, ...) {
  data.frame(
    user_id = x$id %||% NA_character_,
    username = x$username %||% NA_character_,
    displayname = x$displayname %||% NA_character_,
    description = x$description %||% NA_character_,
    followers_count = as.numeric(x$followers_count %||% 0),
    following_count = as.numeric(x$following_count %||% 0),
    tweets_count = as.numeric(x$tweets_count %||% 0),
    verified = isTRUE(x$verified),
    created = x$created %||% NA_character_,
    location = x$location %||% NA_character_,
    url = x$url %||% NA_character_,
    profile_image_url = x$profile_image_url %||% NA_character_,
    stringsAsFactors = FALSE
  )
}

#' @title Filtrar Tweets por Idioma
#' @description Filtra tweets por idioma
#' @param tweets Lista de tweets
#' @param lang Codigo de idioma (ej: "es", "en", "pt")
#' @return Lista filtrada de tweets
#' @export
#' @examples
#' \donttest{
#' tweets <- list(
#'   list(id = "1", lang = "es"),
#'   list(id = "2", lang = "en")
#' )
#' class(tweets) <- c("twscraper_tweets", "list")
#' tweets_es <- filter_by_lang(tweets, "es")
#' }
filter_by_lang <- function(tweets, lang) {
  filtered <- Filter(function(tw) tw$lang == lang, tweets)
  cli::cli_alert_info("Filtrados {length(filtered)} tweets en idioma '{lang}'")
  class(filtered) <- class(tweets)
  return(filtered)
}

#' @title Filtrar Tweets por Fecha
#' @description Filtra tweets por rango de fechas
#' @param tweets Lista de tweets
#' @param from Fecha inicial (POSIXct o character)
#' @param to Fecha final (POSIXct o character)
#' @return Lista filtrada de tweets
#' @export
#' @examples
#' \donttest{
#' tweets <- list(
#'   list(id = "1", date = as.POSIXct("2026-01-01")),
#'   list(id = "2", date = as.POSIXct("2026-02-01"))
#' )
#' class(tweets) <- c("twscraper_tweets", "list")
#' tweets_recent <- filter_by_date(tweets, from = "2025-10-01")
#' }
filter_by_date <- function(tweets, from = NULL, to = NULL) {
  if (!is.null(from)) {
    from <- as.POSIXct(from)
    tweets <- Filter(function(tw) tw$date >= from, tweets)
  }

  if (!is.null(to)) {
    to <- as.POSIXct(to)
    tweets <- Filter(function(tw) tw$date <= to, tweets)
  }

  cli::cli_alert_info("Filtrados {length(tweets)} tweets por fecha")
  class(tweets) <- c("twscraper_tweets", "list")
  return(tweets)
}

#' @title Ordenar Tweets
#' @description Ordena tweets por algun criterio
#' @param tweets Lista de tweets
#' @param by Campo por el cual ordenar ("date", "like_count", "retweet_count", "views_count")
#' @param decreasing Orden descendente (default: TRUE)
#' @return Lista ordenada de tweets
#' @export
#' @examples
#' \donttest{
#' tweets <- list(
#'   list(id = "1", date = as.POSIXct("2026-01-01"), like_count = 2),
#'   list(id = "2", date = as.POSIXct("2026-02-01"), like_count = 10)
#' )
#' class(tweets) <- c("twscraper_tweets", "list")
#' top_tweets <- sort_tweets(tweets, by = "like_count")
#' }
sort_tweets <- function(tweets, by = "date", decreasing = TRUE) {
  sorted <- tweets[order(sapply(tweets, function(x) x[[by]]), decreasing = decreasing)]
  class(sorted) <- class(tweets)
  return(sorted)
}
