#' Extract timezone from an object
#'
#' Generic function to extract the timezone from objects that have timezone information.
#' 
#' @param x An object with timezone information.
#' @param ... Additional arguments passed to methods.
#'
#' @return A character vector representing the timezone of each time point
#'   (e.g., "America/New_York", "UTC").
#' 
#' @examples
#' tz_name(Sys.time())
#' tz_name(as.POSIXct("2024-06-15 12:00:00", tz = "America/New_York"))
#' 
#' @export
tz_name <- S7::new_generic("tz_name", "x")
S7::method(tz_name, class_mixtime) <- function(x) {
  as.character(vecvec::vecvec_apply(x, tz_name))
}
S7::method(tz_name, S7::new_S3_class("mt_time")) <- function(x) {
  rep_len(tz_name(attr(x, "chronon")), length(x))
}
S7::method(tz_name, mt_tz_unit) <- function(x) x@tz
S7::method(tz_name, S7::class_POSIXt) <- function(x) {
  rep_len(attr(x, "tzone") %||% NA_character_, length(x))
}
S7::method(tz_name, S7::class_any) <- function(x) {
  rep_len(NA_character_, length(x))
}

#' Get timezone offset
#'
#' Returns the UTC offset for a given datetime in its specified timezone.
#'
#' @param x A time class coercible to POSIXt with an associated time zone.
#' @param ... Additional arguments passed to methods.
#'
#' @return A numeric vector of offsets from UTC in the same chronon (e.g. seconds for POSIXt, days for dates, etc.)
#' @export
#'
#' @examples
#' tz_offset(as.POSIXct(Sys.time(), tz = Sys.timezone()))
#' tz_offset(as.POSIXct("2024-06-15 12:00:00", tz = "America/New_York"))
tz_offset <- S7::new_generic("tz_offset", "x")
S7::method(tz_offset, S7::class_POSIXt) <- function(x, tz = tz_name(time_chronon(x)), ...) get_tz_offset(x, tz)
S7::method(tz_offset, S7::class_Date) <- function(x, ...) rep.int(0, length(x))
method(tz_offset, class_mixtime) <- vecvec::vecvec_apply_fn(tz_offset, numeric())
method(tz_offset, S7::new_S3_class("mt_time")) <- function(x, tz = tz_name(attr(x, "chronon")), ...) {
  tz_offset_impl(as.numeric(x), attr(x, "chronon"), tz)
}

tz_offset_impl <- function(x, chronon, tz = tz_name(chronon)) {
  offset_s <- rep(0L, length(x))

  # Naive and UTC time has no time zone offsets
  if(is.na(tz) || tz == "UTC") return(offset_s)

  tu_s <- cal_time_civil$second(1L)
  time_s <- chronon_convert_impl(x, chronon, tu_s, FALSE, "UTC")
  offset_s <- get_tz_offset(as.double(time_s), tz)
  nz_offset <- offset_s != 0
  if(!any(nz_offset)) return(rep(0, length(x)))
  offset_s[nz_offset] <- offset_s[nz_offset]*chronon_cardinality(
    chronon, tu_s, time_s[nz_offset]
  )
  offset_s

}

#' Get timezone abbreviation
#'
#' Returns the timezone abbreviation (e.g., "EST", "PDT") for a given datetime
#' in its specified timezone.
#'
#' @param x A POSIXct datetime object or something coercible to POSIXct.
#'   The timezone is extracted from this object.
#' @param tz A character vector of timezones to abbreviate at time point `x`.
#'
#' @return A character vector of timezone abbreviations.
#'
#' @examples
#' tz_abbreviation(Sys.time())
#' tz_abbreviation(as.POSIXct("2024-01-15 12:00:00", tz = "America/New_York"))
#'
#' @export
tz_abbreviation <- function(x, tz = tz_name(x)) {
  tz <- vctrs::vec_recycle(tz, length(x))

  # If tz is empty, then the object has a naive local timezone
  tz_given <- !is.na(tz)

  # TODO: Handle timezone changes within chronon using [before]/[after]
  if (any(tz_given)) {
    tz[tz_given] <- get_tz_abbreviation(
      as.double(chronon_convert(x[tz_given], cal_time_civil$second(1L))),
      tz[tz_given]
    )
  }

  tz
}

#' Get timezone transitions
#'
#' Returns all timezone transitions (e.g., daylight saving time changes) that
#' occur between two datetimes. The timezone is taken from the start datetime.
#'
#' @param start A POSIXct datetime object or something coercible to POSIXct,
#'   representing the start of the time range. The timezone is extracted from this object.
#' @param end A POSIXct datetime object or something coercible to POSIXct,
#'   representing the end of the time range.
#'
#' @return A data frame containing information about timezone transitions
#'   in the specified range.
#'
#' @examples
#' # Get all DST transitions in 2024 for New York
#' tz_transitions(
#'   as.POSIXct("2024-01-01", tz = "America/New_York"),
#'   as.POSIXct("2024-12-31", tz = "America/New_York")
#' )
#' 
#' @export
tz_transitions <- function(start, end) {
  start <- as.double(as.POSIXct(start))
  end <- as.double(as.POSIXct(end))
  tz <- attr(start, "tzone") %||% Sys.timezone()
  get_tz_transitions(start, end, tz)
}
