# basic helpers
char = function(x){ as.character(x) }
num = function(x){ as.numeric(char(x)) }

# quick fn to get our adj mats
adjMat = function(data, valVar, actor1Var, actor2Var){

  # create actor vec
  actors = unique(c(data[[actor1Var]], data[[actor2Var]]))
  n = length(actors)

  # set up empty mat
  mat = matrix(NA, nrow=n, ncol=n, dimnames=list(actors,actors))

  # lazy fill in
  for( ii in 1:nrow(data) ){
    a1 = data[[actor1Var]][ii]
    a2 = data[[actor2Var]][ii]
    val = data[[valVar]][ii]
    mat[a1,a2] = val }

  #
  return(mat) }

# fn to calc basic gof stats
gofstats = function(mat){
  rowSD = sd(apply(mat, 1, mean, na.rm=TRUE))
  colSD = sd(apply(mat, 2, mean, na.rm=TRUE))
  recip = cor(c(mat), c(t(mat)),use='pairwise.complete.obs')
  out = c(rowSD=rowSD, colSD=colSD, recip=recip)
  return(out)
}

# fn to calc basic posterior summary stats
summChain = function(x){
  mu = mean(x)
  qt90s = quantile(x, probs=c(0.05, 0.95))
  qt95s = quantile(x, probs=c(0.025, 0.975))
  out = c(
    lo95 = qt95s[1],
    lo90 = qt90s[1],
    mu = mean(x),
    hi95 = qt95s[2],
    hi90 = qt90s[2] )

  #
  return(out) }

# Align a directed dynamic factor display without changing U_t V_t'.
align_directed_uv = function(fit){
  if (!requireNamespace("abind", quietly = TRUE)) {
    stop("The abind package is required to align the directed profiles.", call. = FALSE)
  }
  n_source = dim(fit$U)[1]
  n_target = dim(fit$V)[1]
  joint_profiles = abind::abind(fit$U, fit$V, along = 1)
  joint_aligned = lame::procrustes_align(U = joint_profiles)$U
  out = fit
  out$U = joint_aligned[seq_len(n_source), , , drop = FALSE]
  out$V = joint_aligned[n_source + seq_len(n_target), , , drop = FALSE]
  surface_change = vapply(
    seq_len(dim(out$U)[3]),
    function(tt) {
      aligned_surface = out$U[, , tt] %*% t(out$V[, , tt])
      max(abs(aligned_surface - fit$Oarr[, , tt]), na.rm = TRUE)
    },
    numeric(1)
  )
  stopifnot(max(surface_change) < 1e-10)
  out
}
