#' Convert between chronons of different time granules
#' 
#' This function converts between chronons measured in different time granules.
#' It is used internally for converting between different continuous time types,
#' and is particularly useful for efficiently converting between irregular time
#' granules. The default method uses `chronon_cardinality()` to cast between 
#' time granules, which is efficient for regular time granules.
#' 
#' @param from The time granule that `x` is measured in (e.g., `day(1L)`).
#' @param to The time granule to convert `x` into (e.g., `week(1L)`).
# #' @param x An integer vector of chronons measured in the `from` time granule.
#' @param ... Additional arguments for methods.
#' 
#' @return An list of two elements:
#' - `div`: integer vector of chronons measured in the `to` time granule.
#' - `mod`: integer vector of the remainder (in `from` time granule) after
#'  converting to the `to` time granule.
#' 
#' @examples
#' # Convert day 16 after epoch (1970-01-01) into weeks since epoch (and remainder days)
#' with(cal_isoweek, chronon_divmod(day(1L), week(1L), 16L))
#' 
#' # Convert week 4 after epoch (1970-W1) into days since epoch
#' with(cal_isoweek, chronon_divmod(week(1L), day(1L), 4L))
#'
#' @export
chronon_divmod <- S7::new_generic("chronon_divmod", c("from", "to"))

# #' @rdname chronon_divmod
S7::method(chronon_divmod, list(mt_unit, mt_unit)) <- function(from, to, x) {
  # No casting needed for identical time granules
  if (identical(S7_class_id(from), S7_class_id(to))) {
    # Shortcut to skip divmod for identical granule divmod ops
    if (identical(from@n, to@n)) {
      return(list(div = vec_data(x), mod = 0L))
    }
    return(fdivmod(vec_data(x), to@n / from@n))
  }

  # Apply graph dispatch to find shortest path between from and to using
  # divmod conversions between time granules (e.g. day -> month)
  path <- S7_graph_dispatch(chronon_divmod_graph(), from, to)

  path[[1]] <- from
  path[[length(path)]] <- to
  # Initialise intermediate classes with 1L and adjacent properties
  path[c(-1, -length(path))] <- lapply(path[c(-1, -length(path))], function(tu){
    # Ideally inherit from `to`, but if incomplete inherit from `from`
    granule_inherit_props(granule_inherit_props(tu(1L), to), from)
  })
  

  chronon <- vector("list", length(path))
  chronon[[1]] <- vec_data(x)
  remainder <- vector("list", length(path)-1L)
  # Forward convert chronons
  for (i in seq(2, length.out = length(path)-1)) {
    result <- chronon_divmod_dispatch(path[[i-1L]], path[[i]], chronon[[i-1L]])
    chronon[[i]] <- result$div
    remainder[[i-1L]] <- result$mod
  }

  # Backward convert remainder
  for (i in seq(length(remainder), by = -1L, length.out = length(remainder) - 1L)) {
    if (remainder[[i]] != 0) {
      remainder[[i-1L]] <- remainder[[i-1L]] + chronon_cardinality(path[[i-1L]], path[[i]], chronon[[i]])*remainder[[i]]
    }
  }

  list(
    div = chronon[[length(chronon)]],
    mod = remainder[[1L]]
  )

}

chronon_divmod_dispatch <- function(from, to, x) {
  if (is.null(chronon_divmod@methods[[S7_class_id(from)]][[S7_class_id(to)]])) {
    return(chronon_divmod_regular(from, to, x))
  } else {
    return(chronon_divmod(from, to, x))
  }
}

## Fallback to chronon_cardinality for regular time granules
chronon_divmod_regular <- function(from, to, x) {
  fdivmod(x, chronon_cardinality(from, to))
}