#' Compute local diffusion exponents with adaptive fitting range
#'
#' For each node, the mean squared resistance displacement is computed exactly
#' via transition matrix powers. Then, for a fixed starting time t_min, we try
#' all possible end times t_max from t_max_min to t_max_max, perform a log-log
#' linear regression, and select the t_max that maximizes the coefficient of
#' determination (R^2). The corresponding H_i is returned.
#'
#' @param adj Symmetric adjacency matrix (0/1, no self-loops) of a connected graph.
#' @param max_time Maximum number of steps (t = 1, ..., max_time) for MSD calculation.
#' @param t_min Fixed starting time for fitting (>= 1).
#' @param t_max_min Minimum end time to try (must be >= t_min + 2 to have at least 3 points).
#' @param t_max_max Maximum end time to try (must be <= max_time).
#' @return A list with components:
#'   \item{H}{Numeric vector of length n, estimated local diffusion exponents.}
#'   \item{best_R2}{Numeric vector of length n, the best R^2 achieved.}
#'   \item{best_tmax}{Integer vector of length n, the t_max that gave the best R^2.}
#'   \item{msd}{Matrix of size max_time x n, where rows are times 1:max_time and columns are nodes.}
#'   \item{fit_objects}{List of lm objects for the best fit (one per node).}
#' @export
#' @importFrom stats lm coef
#' @references
#' Nie, Chun-Xiao. "Vertex‑level diffusion scaling from resistance distance."  .Chaos, Under Review.
#' @examples
#' # Path graph with 4 nodes
#' adj <- matrix(c(0,1,0,0,
#'                 1,0,1,0,
#'                 0,1,0,1,
#'                 0,0,1,0), nrow=4, byrow=TRUE)
#' res <- adaptivelocaldiffexp(adj, max_time = 50,
#'                                            t_min = 10, t_max_min = 20, t_max_max = 40)
#' print(res$H)
adaptivelocaldiffexp <- function(adj, max_time, t_min,
                                 t_max_min, t_max_max) {
  # Input validation
  if (!is.matrix(adj)) stop("adj must be a matrix.")
  if (nrow(adj) != ncol(adj)) stop("adj must be square.")
  if (!isSymmetric(adj)) stop("adj must be symmetric.")
  if (any(diag(adj) != 0)) stop("adj must have zero diagonal.")
  n <- nrow(adj)
  deg <- rowSums(adj)
  if (any(deg == 0)) stop("Graph contains isolated nodes (zero degree).")
  if (t_min < 1) stop("t_min must be >= 1.")
  if (t_max_min < t_min + 2) stop("t_max_min must be at least t_min + 2 to have at least 3 points.")
  if (t_max_max > max_time) stop("t_max_max cannot exceed max_time.")
  if (t_max_min > t_max_max) stop("t_max_min must be <= t_max_max.")

  # Compute resistance distance matrix
  if (!exists("resistance_distance_matrix", mode = "function")) {
    stop("Function resistance_distance_matrix() not found. Please load it.")
  }
  Omega <- resistance_distance_matrix(adj)
  M <- Omega^2   # squared resistance distances

  # Build row-stochastic transition matrix P
  P <- adj / deg

  # Compute exact MSD for all nodes for t = 1..max_time
  msd <- matrix(0, nrow = max_time, ncol = n)
  colnames(msd) <- 1:n
  rownames(msd) <- 1:max_time
  Pt <- diag(n)   # P^0
  for (t in 1:max_time) {
    Pt <- Pt %*% P
    msd[t, ] <- rowSums(Pt * M)
  }

  # Precompute log times (constant for all nodes)
  log_t <- log(t_min:t_max_max)   # we will subset later

  # For each node, try all t_max from t_max_min to t_max_max
  H <- numeric(n)
  best_R2 <- numeric(n)
  best_tmax <- integer(n)
  fit_objects <- vector("list", n)

  for (i in 1:n) {
    best_r2 <- -Inf
    best_t <- NA
    best_fit <- NULL
    best_H <- NA

    for (t_max in t_max_min:t_max_max) {
      # Time indices for this window
      idx <- t_min:t_max
      if (length(idx) < 3) next   # need at least 3 points
      y <- log(msd[idx, i])
      x <- log_t[1:length(idx)]   # corresponding log times
      # Check for finite values
      valid <- is.finite(y) & is.finite(x)
      if (sum(valid) < 3) next
      fit <- lm(y[valid] ~ x[valid])
      r2 <- summary(fit)$r.squared
      if (r2 > best_r2) {
        best_r2 <- r2
        best_t <- t_max
        best_fit <- fit
        best_H <- coef(fit)[2] / 2
      }
    }

    if (best_r2 > -Inf) {
      H[i] <- best_H
      best_R2[i] <- best_r2
      best_tmax[i] <- best_t
      fit_objects[[i]] <- best_fit
    } else {
      H[i] <- NA
      best_R2[i] <- NA
      best_tmax[i] <- NA
      fit_objects[[i]] <- NULL
    }
  }

  return(list(H = H, best_R2 = best_R2, best_tmax = best_tmax,
              msd = msd, fit_objects = fit_objects))
}
