################################################################################
################################################################################
##### META-ANALYSIS: REVISION ANALYSES + MANUSCRIPT FIGURES (NO PRISMA) #####
################################################################################
################################################################################

pacman::p_load(
  dplyr, tidyr, ggplot2, readxl, easystats,
  metafor, meta, conflicted, purrr, broom,
  knitr, kableExtra, gt, DT, htmltools,
  gridExtra, cowplot, viridis, scales
)

conflict_prefer("select", "dplyr")
conflict_prefer("filter", "dplyr")

################################################################################
##### OUTPUT + LOG SETUP #######################################################
################################################################################

out_dir <- "outputs"
if (!dir.exists(out_dir)) dir.create(out_dir, recursive = TRUE)

log_dir <- file.path(out_dir, "logs")
if (!dir.exists(log_dir)) dir.create(log_dir, recursive = TRUE)

log_file <- file.path(
  log_dir,
  paste0("revision_analyses_", format(Sys.time(), "%Y%m%d_%H%M%S"), ".log")
)

log_con <- file(log_file, open = "wt")
sink(log_con, split = TRUE)
sink(log_con, type = "message")

cat("Log started at: ", format(Sys.time()), "\n")
cat("Log file: ", normalizePath(log_file), "\n")
cat("R version: ", R.version.string, "\n")
cat("metafor version: ", as.character(packageVersion("metafor")), "\n\n")

on.exit({
  try(cat("\nLog closed at: ", format(Sys.time()), "\n"), silent = TRUE)
  try(sink(type = "message"), silent = TRUE)
  try(sink(), silent = TRUE)
  try(close(log_con), silent = TRUE)
}, add = TRUE)

cat(strrep("=", 80), "\n")
cat("REVISION ANALYSES - PROSPERO-aligned (PSS predicts metabolic outcomes)\n")
cat("Also creates manuscript figures Figure_2, Figure_3, Figure_4, Figure_4b\n")
cat(strrep("=", 80), "\n\n")

################################################################################
##### DATA LOADING AND CLEANING ################################################
################################################################################

input_file <- "data.xlsx"
data <- readxl::read_excel(input_file)

# Fix dbp_mean ("71, 65" -> mean of 71 and 65)
data <- data %>%
  mutate(
    dbp_mean = case_when(
      dbp_mean == "71, 65" ~ "68.0",
      TRUE ~ as.character(dbp_mean)
    ),
    dbp_mean = as.numeric(dbp_mean)
  )

# Fix hdl_sd (had a date value)
data <- data %>%
  mutate(hdl_sd = as.numeric(as.character(hdl_sd)))

numeric_cols <- c(
  "bmi_mean", "bmi_sd", "wcir_mean", "wcir_sd",
  "sbp_mean", "sbp_sd", "dbp_mean", "dbp_sd",
  "glu_mean", "glu_sd", "homa_mean", "homa_sd",
  "hba1c_mean", "hba1c_sd", "chol_mean", "chol_sd",
  "hdl_mean", "hdl_sd", "ldl_mean", "ldl_sd",
  "trig_mean", "trig_sd", "crp_mean", "crp_sd",
  "pss_mean", "pss_sd", "sample_size", "year", "pss"
)

data <- data %>% mutate(across(any_of(numeric_cols), as.numeric))

if (!"id" %in% names(data)) {
  data <- data %>% mutate(id = dplyr::row_number())
}

# PSS version indicator
data <- data %>%
  mutate(
    pss_version = case_when(
      pss == 10 ~ "PSS-10",
      pss == 14 ~ "PSS-14",
      TRUE ~ NA_character_
    )
  )

# Standardize PSS to z-score across all studies
overall_pss_mean <- mean(data$pss_mean, na.rm = TRUE)
overall_pss_sd   <- sd(data$pss_mean, na.rm = TRUE)

data <- data %>%
  mutate(
    pss_z = (pss_mean - overall_pss_mean) / overall_pss_sd,
    pss_z_var = ifelse(
      !is.na(pss_sd) & !is.na(sample_size) & sample_size > 0,
      (pss_sd^2 / sample_size) / (overall_pss_sd^2),
      NA_real_
    )
  )

cat("Overall PSS mean across", nrow(data), "studies:", round(overall_pss_mean, 2), "\n")
cat("Overall PSS SD:", round(overall_pss_sd, 2), "\n\n")

################################################################################
##### HELPER FUNCTIONS #########################################################
################################################################################

save_plot_pair <- function(plot_obj, stem, width = 10, height = 7) {
  ggplot2::ggsave(
    filename = file.path(out_dir, paste0(stem, ".png")),
    plot = plot_obj,
    width = width,
    height = height,
    dpi = 300,
    bg = "white"
  )
  ggplot2::ggsave(
    filename = file.path(out_dir, paste0(stem, ".pdf")),
    plot = plot_obj,
    width = width,
    height = height,
    device = grDevices::cairo_pdf,
    bg = "white"
  )
}

get_i2_rma <- function(model) {
  if (is.null(model)) return(NA_real_)
  if (!is.null(model$I2)) return(as.numeric(model$I2))
  Q <- model$QE
  df <- model$k - model$p
  max(0, (Q - df) / Q * 100)
}

run_prereg_meta <- function(label, mvar, sdvar) {
  sub <- data %>%
    filter(!is.na(.data[[mvar]]), !is.na(.data[[sdvar]]), .data[[sdvar]] > 0,
           !is.na(pss_z), !is.na(sample_size), sample_size > 0) %>%
    mutate(
      yi = .data[[mvar]],
      vi = (.data[[sdvar]]^2) / sample_size
    )
  
  if (nrow(sub) < 3) {
    cat("---", label, ": insufficient data (k =", nrow(sub), ")\n\n")
    return(NULL)
  }
  
  fit <- metafor::rma(
    yi = yi,
    vi = vi,
    mods = ~ pss_z,
    data = sub,
    method = "REML",
    test = "t"
  )
  
  cat("---", label, "(k =", nrow(sub), ") ---\n\n")
  print(fit)
  cat("\n\n")
  
  list(label = label, data = sub, fit = fit)
}

run_inverse_meta <- function(label, mvar) {
  sub <- data %>%
    filter(!is.na(.data[[mvar]]), !is.na(pss_z), !is.na(pss_z_var), pss_z_var > 0)
  
  if (nrow(sub) < 3) return(NULL)
  
  fit <- metafor::rma.mv(
    yi = pss_z,
    V = pss_z_var,
    mods = stats::as.formula(paste("~", mvar)),
    data = sub,
    method = "REML",
    test = "t",
    random = ~ 1 | id
  )
  
  list(label = label, data = sub, fit = fit)
}

################################################################################
##### 1. PRE-REGISTERED-DIRECTION META-REGRESSIONS #############################
################################################################################

cat(strrep("=", 80), "\n")
cat("1. PRE-REGISTERED-DIRECTION META-REGRESSIONS (PSS predicts metabolic)\n")
cat(strrep("=", 80), "\n\n")

outcomes <- list(
  list("BMI",                  "bmi_mean",   "bmi_sd"),
  list("Waist circumference",  "wcir_mean",  "wcir_sd"),
  list("Systolic BP",          "sbp_mean",   "sbp_sd"),
  list("Diastolic BP",         "dbp_mean",   "dbp_sd"),
  list("Fasting glucose",      "glu_mean",   "glu_sd"),
  list("HOMA-IR",              "homa_mean",  "homa_sd"),
  list("HbA1c",                "hba1c_mean", "hba1c_sd"),
  list("Total cholesterol",    "chol_mean",  "chol_sd"),
  list("HDL cholesterol",      "hdl_mean",   "hdl_sd"),
  list("LDL cholesterol",      "ldl_mean",   "ldl_sd"),
  list("Triglycerides",        "trig_mean",  "trig_sd"),
  list("CRP",                  "crp_mean",   "crp_sd")
)

prereg_results <- lapply(outcomes, function(o) run_prereg_meta(o[[1]], o[[2]], o[[3]]))
names(prereg_results) <- sapply(outcomes, `[[`, 1)

cat("\n", strrep("-", 80), "\n", sep = "")
cat("SUMMARY (pre-registered direction; coefficient is for pss_z)\n")
cat(strrep("-", 80), "\n", sep = "")

summary_tbl <- do.call(rbind, lapply(prereg_results, function(r) {
  if (is.null(r)) return(NULL)
  cf <- coef(summary(r$fit))
  data.frame(
    outcome = r$label,
    k       = r$fit$k,
    beta    = unname(cf[2, "estimate"]),
    ci_lo   = unname(cf[2, "ci.lb"]),
    ci_hi   = unname(cf[2, "ci.ub"]),
    p       = unname(cf[2, "pval"]),
    I2      = round(get_i2_rma(r$fit), 1),
    tau2    = unname(r$fit$tau2)
  )
}))

print(summary_tbl, row.names = FALSE)
write.csv(summary_tbl, file.path(out_dir, "table_S1_prereg_direction_summary.csv"), row.names = FALSE)

################################################################################
##### 2. LEAVE-ONE-OUT SENSITIVITY FOR HOMA-IR #################################
################################################################################

cat("\n", strrep("=", 80), "\n", sep = "")
cat("2. LEAVE-ONE-OUT SENSITIVITY: HOMA-IR (Reviewer Comment 1.4)\n")
cat(strrep("=", 80), "\n\n", sep = "")

homa_sub <- data %>%
  filter(!is.na(homa_mean), !is.na(homa_sd), homa_sd > 0,
         !is.na(pss_z), !is.na(sample_size), sample_size > 0) %>%
  mutate(yi = homa_mean, vi = (homa_sd^2) / sample_size)

cat("k =", nrow(homa_sub), "HOMA-IR studies:\n")
print(homa_sub %>% select(firstauthor, year, sample_size, pss, pss_mean, homa_mean))
cat("\n")

loo_rows <- list()
for (i in seq_len(nrow(homa_sub))) {
  omitted <- paste0(homa_sub$firstauthor[i], " (", homa_sub$year[i], ")")
  sub_i   <- homa_sub[-i, , drop = FALSE]
  fit_i   <- metafor::rma(yi = yi, vi = vi, mods = ~ pss_z, data = sub_i, method = "REML", test = "t")
  cf_i    <- coef(summary(fit_i))
  loo_rows[[i]] <- data.frame(
    omitted     = omitted,
    k_remaining = fit_i$k,
    beta        = unname(cf_i[2, "estimate"]),
    ci_lo       = unname(cf_i[2, "ci.lb"]),
    ci_hi       = unname(cf_i[2, "ci.ub"]),
    p           = unname(cf_i[2, "pval"])
  )
}
loo_tbl <- do.call(rbind, loo_rows)
print(loo_tbl, row.names = FALSE)
write.csv(loo_tbl, file.path(out_dir, "table_S3_HOMA-IR_leave_one_out.csv"), row.names = FALSE)

################################################################################
##### 3. HOMA-IR FOREST PLOT (FIGURE 4b) #######################################
################################################################################

cat("\n", strrep("=", 80), "\n", sep = "")
cat("3. HOMA-IR FOREST PLOT (Reviewer Comment 1.4)\n")
cat(strrep("=", 80), "\n\n", sep = "")

homa_pool <- metafor::rma(yi = yi, vi = vi, data = homa_sub, method = "REML", test = "t")
print(homa_pool)

slab_labels <- paste0(homa_sub$firstauthor, " (", homa_sub$year, ")", "   (N=", homa_sub$sample_size, ")")

png(file.path(out_dir, "Figure_4b.png"), width = 1800, height = 900, res = 200)
metafor::forest(
  homa_pool,
  slab   = slab_labels,
  xlab   = "HOMA-IR (study mean, 95% CI)",
  header = c("Study", "HOMA-IR [95% CI]"),
  cex    = 0.9
)
title(main = sprintf("HOMA-IR forest plot (k=%d); I2 = %.1f%%, tau2 = %.3f",
                     nrow(homa_sub), homa_pool$I2, homa_pool$tau2))
dev.off()

pdf(file.path(out_dir, "Figure_4b.pdf"), width = 9, height = 4.5)
metafor::forest(
  homa_pool,
  slab   = slab_labels,
  xlab   = "HOMA-IR (study mean, 95% CI)",
  header = c("Study", "HOMA-IR [95% CI]"),
  cex    = 0.9
)
title(main = sprintf("HOMA-IR forest plot (k=%d); I2 = %.1f%%, tau2 = %.3f",
                     nrow(homa_sub), homa_pool$I2, homa_pool$tau2))
dev.off()

cat("\nForest plot saved to:\n  ",
    file.path(out_dir, "Figure_4b.png"), "\n  ",
    file.path(out_dir, "Figure_4b.pdf"), "\n", sep = "")

################################################################################
##### 4. MODERATOR / SUBGROUP ANALYSES #########################################
################################################################################

cat("\n", strrep("=", 80), "\n", sep = "")
cat("4. PRE-SPECIFIED MODERATOR / SUBGROUP ANALYSES\n")
cat(strrep("=", 80), "\n\n", sep = "")
cat("Note: The PROSPERO protocol pre-specified subgroup analyses by age,\n")
cat("ethnicity, and BMI. Study-level age and ethnicity were not consistently\n")
cat("reported across primary studies and cannot be reliably extracted from\n")
cat("the current dataset; we therefore report PSS-version and BMI-stratum\n")
cat("subgroup analyses for BMI (the outcome with the largest k).\n\n")

bmi_sub <- data %>%
  filter(!is.na(bmi_mean), !is.na(bmi_sd), bmi_sd > 0,
         !is.na(pss_z), !is.na(sample_size), sample_size > 0) %>%
  mutate(yi = bmi_mean, vi = (bmi_sd^2) / sample_size)

cat("--- BMI by PSS version ---\n")
for (v in c(10, 14)) {
  s <- bmi_sub %>% filter(pss == v)
  if (nrow(s) < 3) {
    cat("  PSS-", v, ": k=", nrow(s), " (insufficient)\n", sep = "")
  } else {
    fit <- metafor::rma(yi = yi, vi = vi, mods = ~ pss_z, data = s, method = "REML", test = "t")
    cf  <- coef(summary(fit))
    cat(sprintf("  PSS-%d: k=%d, beta=%.4f [%.3f, %.3f], p=%.3f, I2=%.1f%%\n",
                v, fit$k, cf[2, "estimate"], cf[2, "ci.lb"], cf[2, "ci.ub"],
                cf[2, "pval"], get_i2_rma(fit)))
  }
}

cat("\n--- BMI by study-level BMI category (split at median) ---\n")
med_bmi <- median(bmi_sub$bmi_mean, na.rm = TRUE)
cat("Median study BMI =", round(med_bmi, 2), "\n")
for (lab_cond in list(
  list("BMI <= median", bmi_sub$bmi_mean <= med_bmi),
  list("BMI >  median", bmi_sub$bmi_mean >  med_bmi)
)) {
  lab <- lab_cond[[1]]
  s   <- bmi_sub[lab_cond[[2]], , drop = FALSE]
  if (nrow(s) < 3) {
    cat(" ", lab, ": k=", nrow(s), " (insufficient)\n", sep = "")
  } else {
    fit <- metafor::rma(yi = yi, vi = vi, mods = ~ pss_z, data = s, method = "REML", test = "t")
    cf  <- coef(summary(fit))
    cat(sprintf("  %s: k=%d, beta=%.4f [%.3f, %.3f], p=%.3f\n",
                lab, fit$k, cf[2, "estimate"], cf[2, "ci.lb"], cf[2, "ci.ub"], cf[2, "pval"]))
  }
}

################################################################################
##### 5. SENSITIVITY: ORIGINAL (INVERSE) PARAMETERIZATION ######################
################################################################################

cat("\n", strrep("=", 80), "\n", sep = "")
cat("5. SENSITIVITY: ORIGINAL (INVERSE) PARAMETERIZATION - Supp Table S2\n")
cat(strrep("=", 80), "\n", sep = "")
cat("yi = pss_z, mods = ~ metabolic_predictor\n\n")

inverse_rows <- list()
for (o in outcomes) {
  label <- o[[1]]
  mvar  <- o[[2]]
  r <- run_inverse_meta(label, mvar)
  if (is.null(r)) next
  cf <- coef(summary(r$fit))
  inverse_rows[[length(inverse_rows) + 1]] <- data.frame(
    outcome = label,
    k       = r$fit$k,
    beta    = unname(cf[2, "estimate"]),
    ci_lo   = unname(cf[2, "ci.lb"]),
    ci_hi   = unname(cf[2, "ci.ub"]),
    p       = unname(cf[2, "pval"]),
    I2      = round(get_i2_rma(r$fit), 1)
  )
}

inverse_tbl <- do.call(rbind, inverse_rows)
print(inverse_tbl, row.names = FALSE)
write.csv(inverse_tbl, file.path(out_dir, "table_S2_inverse_parameterization.csv"), row.names = FALSE)

################################################################################
##### 6. MANUSCRIPT FIGURE 2 ###################################################
################################################################################

cat("\n", strrep("=", 80), "\n", sep = "")
cat("6. FIGURE 2 - Distribution of PSS scores by scale version\n")
cat(strrep("=", 80), "\n\n", sep = "")

fig2_data <- data %>% filter(!is.na(pss_mean), !is.na(pss_version))

p_fig2 <- ggplot(fig2_data, aes(x = pss_version, y = pss_mean, fill = pss_version)) +
  geom_violin(alpha = 0.35, trim = FALSE, color = NA) +
  geom_boxplot(width = 0.18, outlier.shape = NA, alpha = 0.9) +
  geom_jitter(width = 0.08, alpha = 0.75, size = 2) +
  scale_fill_manual(values = c("PSS-10" = "#d95f02", "PSS-14" = "#1b9e77")) +
  labs(
    title = "Distribution of PSS scores by scale version",
    x = "Scale version",
    y = "PSS mean score"
  ) +
  theme_minimal(base_size = 13) +
  theme(legend.position = "none")

save_plot_pair(p_fig2, "Figure_2", width = 10, height = 7)
cat("Saved Figure_2.png and Figure_2.pdf\n")

################################################################################
##### 7. MANUSCRIPT FIGURE 3 ###################################################
################################################################################

cat("\n", strrep("=", 80), "\n", sep = "")
cat("7. FIGURE 3 - Standardised PSS z-scores across studies\n")
cat(strrep("=", 80), "\n\n", sep = "")

fig3_data <- data.frame(
  study       = paste(data$firstauthor, data$year),
  pss_z       = data$pss_z,
  se          = sqrt(data$pss_z_var),
  pss_version = data$pss_version,
  sample_size = data$sample_size
) %>%
  filter(!is.na(pss_z), !is.na(se), !is.na(pss_version), !is.na(sample_size)) %>%
  arrange(desc(pss_z)) %>%
  mutate(
    ci_lower    = pss_z - 1.96 * se,
    ci_upper    = pss_z + 1.96 * se,
    study_label = paste0(study, " (n=", sample_size, ")")
  )

p_fig3 <- ggplot(fig3_data, aes(x = pss_z, y = reorder(study_label, pss_z))) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray") +
  geom_errorbarh(aes(xmin = ci_lower, xmax = ci_upper, color = pss_version),
                 height = 0.3, alpha = 0.7) +
  geom_point(aes(size = sample_size, color = pss_version), alpha = 0.8) +
  scale_color_manual(values = c("PSS-10" = "#d95f02", "PSS-14" = "#1b9e77")) +
  labs(
    title  = "Standardised PSS z-scores across studies",
    x      = "PSS z-score",
    y      = NULL,
    color  = "PSS Version",
    size   = "Sample Size"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position       = "bottom",
    panel.grid.major.y    = element_blank()
  )

save_plot_pair(p_fig3, "Figure_3", width = 10, height = 12)
cat("Saved Figure_3.png and Figure_3.pdf\n")

################################################################################
##### 8. MANUSCRIPT FIGURE 4 ###################################################
################################################################################

cat("\n", strrep("=", 80), "\n", sep = "")
cat("8. FIGURE 4 - Meta-regression coefficients\n")
cat(strrep("=", 80), "\n\n", sep = "")

coef_data <- summary_tbl %>%
  mutate(
    significant      = p < 0.05,
    predictor_label  = paste0(outcome, " (k=", k, ")")
  )

p_fig4 <- ggplot(coef_data, aes(x = beta, y = reorder(predictor_label, beta))) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
  geom_errorbarh(aes(xmin = ci_lo, xmax = ci_hi, color = significant),
                 height = 0.28, linewidth = 0.8) +
  geom_point(aes(color = significant, size = k), alpha = 0.9) +
  scale_color_manual(values = c("TRUE" = "#b2182b", "FALSE" = "#4d4d4d")) +
  labs(
    title = "Meta-regression coefficients showing how stress predicts metabolic dysfunction",
    x     = expression(beta ~ "for PSS z-score"),
    y     = NULL,
    color = "p < 0.05",
    size  = "Studies"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")

save_plot_pair(p_fig4, "Figure_4", width = 10, height = 8)
cat("Saved Figure_4.png and Figure_4.pdf\n")

################################################################################
##### 9. SESSION INFO ###########################################################
################################################################################

cat("\n", strrep("=", 80), "\n", sep = "")
cat("SESSION INFO\n")
cat(strrep("=", 80), "\n\n", sep = "")
print(sessionInfo())
cat("\nAll output saved under: ", normalizePath(out_dir), "\n", sep = "")