library(shiny)
library(shinyjs)
library(ggplot2)
library(dplyr)
library(tidyr)
library(purrr)
library(stringr)
library(xml2)
library(DiagrammeR)
library(DiagrammeRsvg)
library(igraph)
library(tidySEM)
library(DT)
library(colourpicker)
library(grid)
library(svglite)
library(grDevices)
library(lavaan)
library(blavaan)
library(semPlot)

options(shiny.maxRequestSize = 500 * 1024^2)  # 500MB

safe_numeric_input <- function(input_value, default = 0) {
  if (!is.null(input_value) &&
      input_value != "" &&
      !is.na(as.numeric(input_value))) {
    as.numeric(input_value)
  } else {
    default
  }
}

`%||%` <- function(a, b) if (!is.null(a)) a else b

generate_sem_code <- function(model_type, lavaan_syntax) {
  base_code <- switch(
    model_type,
    "cfa" = "cfa",
    "path" = "sem",
    "sem" = "sem",
    "growth" = "growth"
  )

  common_params <- "lavaan_string, data = data"

  additional_params <- switch(
    model_type,
    "cfa" = ", std.lv = TRUE",
    "growth" = ", missing = 'ml'",
    ""  # Default empty
  )

  res <- paste0(base_code, "(", common_params, additional_params, ")")
  return(res)
}

auto_generate_loops <- function(points_data, loop_radius = 1, loop_width = 1, loop_height = 1,
                                gap_size = 0.2, orientation = 0, arrow_type = "closed",
                                arrow_size = 0.2, two_way = FALSE, loop_color = "#000000",
                                alpha = 1, line_width = 1, which_group) {

  # Filter out locked points
  unlocked_points <- points_data[!points_data$locked & points_data$group == which_group,  c("x", "y")]

  if (nrow(unlocked_points) < 1) {
    return(NULL)
  }

  # Generate self-loop arrows
  loops_df <- data.frame(
    x_center = unlocked_points$x,
    y_center = unlocked_points$y,
    color = loop_color,
    width = line_width,
    alpha = alpha,
    arrow_type = arrow_type,
    arrow_size = arrow_size,
    gap_size = gap_size,
    loop_width = loop_width,
    loop_height = loop_height,
    radius = loop_radius,
    orientation = orientation,
    lavaan = FALSE,
    two_way = two_way,
    locked = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )


  return(loops_df)
}



generate_letter_sequence <- function(n, start_value = 1) {
  letter_sequence <- sapply(
    seq_len(ceiling((start_value + n - 1) / length(LETTERS))),
    function(x) paste0(LETTERS, ifelse(x > 1, x - 1, ""))
  )
  letter_sequence <- unlist(letter_sequence)

  return(letter_sequence[start_value:(start_value + n - 1)])
}

auto_generate_text <- function(points_data, text_type = "default", text = "Text", start_value = 1,
                               text_color = "#000000", text_fill = NA, text_size = 20,
                               font = "sans", alpha = 1, fontface = "plain", orientation = 0, which_group = "1") {

  # Filter out locked points
  unlocked_points <- points_data[!points_data$locked & points_data$group == which_group,  c("x", "y")]

  if (nrow(unlocked_points) < 1) {
    return(NULL)
  }

  if (text_type == "sequence_numbers") {
    text_values <- as.character(seq(start_value, by = 1, length.out = nrow(unlocked_points)))
  } else if (text_type == "sequence_letters") {
    text_values <- generate_letter_sequence(nrow(unlocked_points), start_value)
  } else {
    text_values <- rep(text, nrow(unlocked_points))
  }

  # Generate text annotations
  text_df <- data.frame(
    text = text_values,
    x = unlocked_points$x,
    y = unlocked_points$y,
    font = font,
    size = text_size,
    color = text_color,
    fill = text_fill,
    angle = orientation,
    alpha = alpha,
    fontface = fontface,
    math_expression = FALSE,
    hjust = 0.5,
    vjust = 0.5,
    lavaan = FALSE,
    network = FALSE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  return(text_df)
}

generate_fit_stats_annotations <- function(
    model_fit,
    chisq = FALSE,
    cfi_tli = FALSE,
    rmsea = FALSE,
    srmr = FALSE,
    ppp = FALSE,
    dic = FALSE,
    waic = FALSE,
    looic = FALSE,
    text_stats_size = 16,
    text_stats_color = "#000000",
    text_stats_fill = NA,
    text_stats_font = "sans",
    text_stats_fontface = "plain",
    text_stats_alpha = 1,
    text_stats_angle = 0,
    which_group = "1",
    x_stats_location = 0.5,
    y_stats_location = 0.9,
    text_stats_hjust = 0.5,  # Center by default
    text_stats_vjust = 0.5,  # Middle by default
    is_blavaan = FALSE
) {

  # Calculate fit measures with error handling
  stats <- tryCatch(
    {
      if (is.null(model_fit)) {
        return(NULL)
      }

      # Create vector of requested measures
      measures <- c()
      if (chisq) measures <- c(measures, "chisq", "df", "pvalue")
      if (cfi_tli) measures <- c(measures, "cfi", "tli")
      if (rmsea) measures <- c(measures, "rmsea", "rmsea.ci.lower", "rmsea.ci.upper")
      if (srmr) measures <- c(measures, "srmr")

      if (is_blavaan) {
        if (ppp) measures <- c(measures, "ppp")
        if (dic) measures <- c(measures, "dic")
        if (waic) measures <- c(measures, "waic")
        if (looic) measures <- c(measures, "looic")
      }

      if (length(measures) == 0) {
        return(NULL)
      }

      fitMeasures(model_fit, measures)
    },
    error = function(e) {
      return(NULL)
    }
  )

  if (is.null(stats)) {
    return(NULL)
  }

  # Build the text with line breaks
  text_lines <- character(0)

  if (is_blavaan) {
    # Bayesian fit statistics
    if (ppp && "ppp" %in% names(stats)) {
      text_lines <- c(text_lines, sprintf("Posterior Predictive P-value (PPP) = %.3f", stats["ppp"]))
    }

    if (dic && "dic" %in% names(stats)) {
      text_lines <- c(text_lines, sprintf("DIC = %.3f", stats["dic"]))
    }

    if (waic && "waic" %in% names(stats)) {
      text_lines <- c(text_lines, sprintf("WAIC = %.3f", stats["waic"]))
    }

    if (looic && "looic" %in% names(stats)) {
      text_lines <- c(text_lines, sprintf("LOOIC = %.3f", stats["looic"]))
    }
  } else {
    # Frequentist fit statistics
    if (chisq && all(c("chisq", "df", "pvalue") %in% names(stats))) {
      # Format p-value properly
      p_value <- stats["pvalue"]
      p_text <- if (p_value < 0.001) {
        "p < 0.001"
      } else {
        sprintf("p = %.3f", p_value)
      }

      text_lines <- c(text_lines,
                      sprintf("χ²(%d) = %.2f, %s",
                              stats["df"], stats["chisq"], p_text))
    }

    if (cfi_tli && all(c("cfi", "tli") %in% names(stats))) {
      text_lines <- c(text_lines, sprintf("CFI = %.3f", stats["cfi"]))
      text_lines <- c(text_lines, sprintf("TLI = %.3f", stats["tli"]))
    }

    if (rmsea && all(c("rmsea", "rmsea.ci.lower", "rmsea.ci.upper") %in% names(stats))) {
      text_lines <- c(text_lines,
                      sprintf("RMSEA = %.3f [%.3f, %.3f]",
                              stats["rmsea"], stats["rmsea.ci.lower"], stats["rmsea.ci.upper"]))
    }

    if (srmr && "srmr" %in% names(stats)) {
      text_lines <- c(text_lines, sprintf("SRMR = %.3f", stats["srmr"]))
    }
  }

  # Combine all lines with \n
  if (length(text_lines) == 0) { #
    return(NULL)
  }

  final_text <- paste(text_lines, collapse = "\n")

  # Determine fill color
  fill_color <- if (!is.null(text_stats_fill) && !is.na(text_stats_fill)) {
    text_stats_fill
  } else {
    NA
  }

  # Create the data frame with a single row
  text_df <- data.frame(
    text = final_text,
    x = x_stats_location,
    y = y_stats_location,
    font = text_stats_font,
    size = text_stats_size,
    color = text_stats_color,
    fill = fill_color,
    angle = text_stats_angle,
    alpha = text_stats_alpha,
    fontface = text_stats_fontface,
    math_expression = FALSE,
    hjust = text_stats_hjust,
    vjust = text_stats_vjust,
    lavaan = FALSE,
    network = FALSE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  return(text_df)
}

valid_hex <- function(x) {
  if (grepl("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", x)) {
    return(x)
  } else if (is.na(x)) {
    return(NA)
  } else {
    return("#000000")  # Default to black or another fallback color
  }
}

valid_line_style <- function(x) {
  valid_styles <- c("dotted", "dashed", "solid")
  if (x %in% valid_styles) {
    return(x)
  } else {
    return("solid") # Default to solid
  }
}

valid_fontface <- function(x) {
  valid_faces <- c("plain", "bold", "italic")
  if (x %in% valid_faces) {
    return(x)
  } else {
    return("plain") # Default to plain
  }
}

valid_font <- function(x) {
  valid_fonts <- c("sans", "mono", "serif")
  if (x %in% valid_fonts) {
    return(x)
  } else {
    return("sans") # Default to sans
  }
}

valid_type <- function(x) {
  valid_types <- c("Straight Line", "Straight Arrow", "Curved Line", "Curved Arrow")
  if (x %in% valid_types) {
    return(x)
  } else {
    return("Straight Line") # Default to "Straight Line"
  }
}

valid_gradient_position <- function(x) {
  x <- as.numeric(x)
  if (!is.na(x) && x >= 0 && x <= 1) {
    return(x)
  } else {
    return(0.5) # Default to midpoint
  }
}

valid_alpha <- function(x) {
  x <- as.numeric(x)
  if (!is.na(x) && x >= 0 && x <= 1) {
    return(x)
  } else {
    return(1) # Default to fully opaque
  }
}

valid_shape <- function(x) {
  valid_shapes <- c("circle", "square", "oval", "triangle", "rectangle", "diamond")
  if (x %in% valid_shapes) {
    return(x)
  } else {
    return("circle") # Default to circle
  }
}

valid_logical <- function(x) {
  x <- toupper(x)
  if (x %in% c("TRUE", "T", "YES", "1")) {
    return(TRUE)
  } else if (x %in% c("FALSE", "F", "NO", "0")) {
    return(FALSE)
  } else {
    return(FALSE) # Default to FALSE
  }
}

to_hex2 <- function(color) {
  if (grepl("^#[0-9A-Fa-f]{6}$", color, ignore.case = TRUE)) {
    return(color)
  }
  else if (grepl("^#[0-9A-Fa-f]{8}$", color, ignore.case = TRUE)) {
    return(substr(color, 1, 7))
  }
  else if (color %in% colors()) {
    rgb_matrix <- col2rgb(color)
    return(rgb(rgb_matrix[1,], rgb_matrix[2,], rgb_matrix[3,], maxColorValue = 255))
  }
  else {
    return(color)
  }
}

get_alpha <- function(hex_color) {
  if (grepl("^#[0-9A-Fa-f]{8}$", hex_color, ignore.case = TRUE)) {
    alpha_hex <- substr(hex_color, 8, 9)  # Extract last 2 digits (AA)
  }
  else if (grepl("^#[0-9A-Fa-f]{4}$", hex_color, ignore.case = TRUE)) {
    alpha_hex <- paste0(
      substr(hex_color, 4, 4),  # Short hex (RGBA) doubles digits (A -> AA)
      substr(hex_color, 4, 4)
    )
  }
  else {
    return(1)
  }

  alpha_decimal <- strtoi(paste0("0x", alpha_hex)) / 255
  return(alpha_decimal)
}


draw_points <- function(points_data, zoom_level = 1) {
  if (nrow(points_data) == 0) return(NULL)

  # Vectorize validation functions
  points_data$color <- sapply(points_data$color, valid_hex)
  points_data$border_color <- sapply(points_data$border_color, valid_hex)
  points_data$shape <- sapply(points_data$shape, valid_shape)
  points_data$alpha <- sapply(points_data$alpha, valid_alpha)
  points_data$locked <- sapply(points_data$locked, valid_logical)

  # Recycle short vectors
  if (length(points_data$color) != nrow(points_data)) {
    points_data$color <- rep(points_data$color[1], nrow(points_data))
  }

  if (length(points_data$border_width) != nrow(points_data)) {
    points_data$border_width <- rep(points_data$border_width[1], nrow(points_data))
  }

  # Pre-calculate common values
  min_size_factor <- 0.25
  scale_factor <- sqrt(2)
  adjusted_strokes <- points_data$border_width / zoom_level

  # Pre-allocate layers list
  layers <- vector("list", nrow(points_data))

  for (i in seq_len(nrow(points_data))) {
    shape <- points_data$shape[i]
    width_height_ratio <- ifelse(!is.null(points_data$width_height_ratio[i]),
                                 points_data$width_height_ratio[i], 1)

    # Calculate adjusted dimensions based on shape
    adjusted_width <- switch(shape,
                             "circle" = points_data$size[i] / scale_factor * min_size_factor,
                             "square" = points_data$size[i] * sqrt(2) * min_size_factor,
                             "triangle" = points_data$size[i] * sqrt(4 / sqrt(3)) * min_size_factor,
                             "rectangle" = points_data$size[i] * min_size_factor * width_height_ratio,
                             "diamond" = points_data$size[i] * 1.4 * sqrt(1.5) * min_size_factor * width_height_ratio,
                             "oval" = points_data$size[i] * min_size_factor / scale_factor * width_height_ratio,
                             points_data$size[i] / 3  # default
    )

    adjusted_height <- switch(shape,
                              "circle" = adjusted_width,
                              "square" = adjusted_width,
                              "triangle" = adjusted_width * sqrt(3) / 2,
                              "rectangle" = points_data$size[i] * min_size_factor,
                              "diamond" = points_data$size[i] * 1.4 * sqrt(1.5) * min_size_factor,
                              "oval" = points_data$size[i] * min_size_factor / scale_factor,
                              adjusted_width / width_height_ratio  # default
    )


    # Generate coordinates based on shape
    if (shape == "circle") {
      t <- seq(0, 2 * pi, length.out = 100)
      x_coords <- points_data$x[i] + adjusted_width * cos(t)
      y_coords <- points_data$y[i] + adjusted_height * sin(t)
      coords <- data.frame(x = x_coords, y = y_coords)
    } else {
      coords <- switch(shape,
                       "triangle" = data.frame(
                         x = c(points_data$x[i], points_data$x[i] - adjusted_width/2, points_data$x[i] + adjusted_width/2),
                         y = c(points_data$y[i] + adjusted_height/2, points_data$y[i] - adjusted_height/2, points_data$y[i] - adjusted_height/2)
                       ),
                       "square" = data.frame(
                         x = c(
                           points_data$x[i] - adjusted_width/2, points_data$x[i] + adjusted_width/2,
                           points_data$x[i] + adjusted_width/2, points_data$x[i] - adjusted_width/2
                         ),
                         y = c(
                           points_data$y[i] - adjusted_height/2, points_data$y[i] - adjusted_height/2,
                           points_data$y[i] + adjusted_height/2, points_data$y[i] + adjusted_height/2
                         )
                       ),
                       "oval" = {
                         t <- seq(0, 2 * pi, length.out = 100)
                         data.frame(
                           x = points_data$x[i] + adjusted_width * cos(t),
                           y = points_data$y[i] + adjusted_height * sin(t)
                         )
                       },
                       "rectangle" = data.frame(
                         x = c(
                           points_data$x[i] - adjusted_width/2, points_data$x[i] + adjusted_width/2,
                           points_data$x[i] + adjusted_width/2, points_data$x[i] - adjusted_width/2
                         ),
                         y = c(
                           points_data$y[i] - adjusted_height/2, points_data$y[i] - adjusted_height/2,
                           points_data$y[i] + adjusted_height/2, points_data$y[i] + adjusted_height/2
                         )
                       ),
                       # diamond (default case)
                       data.frame(
                         x = c(
                           points_data$x[i], points_data$x[i] - adjusted_width/2,
                           points_data$x[i], points_data$x[i] + adjusted_width/2
                         ),
                         y = c(
                           points_data$y[i] + adjusted_height/2, points_data$y[i],
                           points_data$y[i] - adjusted_height/2, points_data$y[i]
                         )
                       )
      )

      # Rotate if needed (all shapes except circle)
      if (!is.null(points_data$orientation[i])) {
        coords <- rotate_coords(coords$x, coords$y, points_data$orientation[i],
                                cx = points_data$x[i], cy = points_data$y[i])
      }
    }

    # Create annotation layer
    layers[[i]] <- annotate(
      "polygon",
      x = coords$x,
      y = coords$y,
      fill = points_data$color[i],
      colour = points_data$border_color[i],
      linewidth = adjusted_strokes[i],
      alpha = points_data$alpha[i]
    )
  }

  return(layers)
}

draw_lines <- function(lines_data, zoom_level = 1) {
  if (nrow(lines_data) == 0) return(list())

  # Input validation
  lines_data$color <- sapply(lines_data$color, valid_hex)
  lines_data$end_color <- sapply(lines_data$end_color, valid_hex)
  lines_data$line_style <- sapply(lines_data$line_style, valid_line_style)
  lines_data$alpha <- sapply(lines_data$alpha, valid_alpha)
  lines_data$gradient_position <- sapply(lines_data$gradient_position, valid_gradient_position)
  lines_data$type <- sapply(lines_data$type, valid_type)
  lines_data$locked <- sapply(lines_data$locked, valid_logical)

  # Process each line and collect layers
  layers <- lapply(1:nrow(lines_data), function(i) {
    line_type <- lines_data$type[i]
    start_color <- lines_data$color[i]
    end_color <- if (length(lines_data$color_type[i]) > 0 && lines_data$color_type[i] == "Gradient") {
      lines_data$end_color[i]
    } else {
      start_color
    }

    gradient_position <- if (!is.null(lines_data$gradient_position[i]) &&
                             length(lines_data$gradient_position[i]) > 0) {
      lines_data$gradient_position[i]
    } else {
      1
    }

    if (is.null(line_type) || length(line_type) == 0) return(NULL)

    adjusted_line_width <- lines_data$width[i] / zoom_level
    adjusted_arrow_size <- if (!is.na(lines_data$arrow_size[i]) &&
                               lines_data$type[i] %in% c('Straight Arrow', 'Curved Arrow')) {
      lines_data$arrow_size[i] / zoom_level
    } else {
      NA
    }

    line_layers <- list()

    if (line_type == "Straight Line" || line_type == "Straight Arrow") {
      if (!is.null(lines_data$x_start[i]) && !is.null(lines_data$x_end[i])) {
        if (lines_data$color_type[i] == "Gradient") {
          straight_points <- interpolate_points(
            x_start = lines_data$x_start[i], y_start = lines_data$y_start[i],
            x_end = lines_data$x_end[i], y_end = lines_data$y_end[i]
          )

          n_points <- nrow(straight_points)
          split_index <- round(gradient_position * n_points)

          color_interpolator <- colorRampPalette(c(start_color, end_color))
          intermediate_color <- color_interpolator(3)[2]

          gradient_colors_start <- colorRampPalette(c(start_color, intermediate_color))(split_index)
          gradient_colors_end <- colorRampPalette(c(intermediate_color, end_color))(n_points - split_index + 1)

          # Add gradient segments
          for (j in 1:(split_index - 1)) {
            line_layers[[length(line_layers) + 1]] <- annotate(
              "segment",
              x = straight_points$x[j], y = straight_points$y[j],
              xend = straight_points$x[j + 1], yend = straight_points$y[j + 1],
              color = gradient_colors_start[j],
              linewidth = adjusted_line_width,
              alpha = lines_data$alpha[i]
            )
          }

          for (j in split_index:(n_points - 1)) {
            line_layers[[length(line_layers) + 1]] <- annotate(
              "segment",
              x = straight_points$x[j], y = straight_points$y[j],
              xend = straight_points$x[j + 1], yend = straight_points$y[j + 1],
              color = gradient_colors_end[j - split_index + 1],
              linewidth = adjusted_line_width,
              alpha = lines_data$alpha[i]
            )
          }
        } else {
          # Single color line
          line_layers[[length(line_layers) + 1]] <- annotate(
            "segment",
            x = lines_data$x_start[i], y = lines_data$y_start[i],
            xend = lines_data$x_end[i], yend = lines_data$y_end[i],
            color = start_color,
            linewidth = adjusted_line_width,
            alpha = lines_data$alpha[i],
            linetype = lines_data$line_style[i]
          )
        }

        # Add arrowhead if needed
        arrow_type <- lines_data$arrow_type[i]
        if (!is.null(arrow_type) && !is.na(adjusted_arrow_size)) {
          dx <- lines_data$x_end[i] - lines_data$x_start[i]
          dy <- lines_data$y_end[i] - lines_data$y_start[i]
          norm <- sqrt(dx^2 + dy^2)

          x_adjust_end <- lines_data$x_end[i] - 0.01 * dx / norm
          y_adjust_end <- lines_data$y_end[i] - 0.01 * dy / norm

          if (lines_data$two_way[i]) {
            x_adjust_start <- lines_data$x_start[i] + 0.01 * dx / norm
            y_adjust_start <- lines_data$y_start[i] + 0.01 * dy / norm

            line_layers[[length(line_layers) + 1]] <- annotate(
              "segment",
              x = x_adjust_start, y = y_adjust_start,
              xend = lines_data$x_start[i], yend = lines_data$y_start[i],
              linewidth = adjusted_line_width,
              alpha = lines_data$alpha[i],
              arrow = arrow(type = arrow_type, length = unit(adjusted_arrow_size, "inches")),
              color = if (lines_data$color_type[i] == "Gradient") gradient_colors_start[1] else start_color
            )
          }

          line_layers[[length(line_layers) + 1]] <- annotate(
            "segment",
            x = x_adjust_end, y = y_adjust_end,
            xend = lines_data$x_end[i], yend = lines_data$y_end[i],
            linewidth = adjusted_line_width,
            alpha = lines_data$alpha[i],
            arrow = arrow(type = arrow_type, length = unit(adjusted_arrow_size, "inches")),
            color = if (lines_data$color_type[i] == "Gradient") gradient_colors_end[length(gradient_colors_end)] else end_color
          )
        }
      }
    }

    if (line_type == "Curved Line" || line_type == "Curved Arrow") {
      if (!is.null(lines_data$ctrl_x[i]) && !is.null(lines_data$ctrl_y[i])) {
        bezier_points <- create_bezier_curve(
          x_start = lines_data$x_start[i], y_start = lines_data$y_start[i],
          x_end = lines_data$x_end[i], y_end = lines_data$y_end[i],
          ctrl_x = lines_data$ctrl_x[i], ctrl_y = lines_data$ctrl_y[i],
          ctrl_x2 = lines_data$ctrl_x2[i], ctrl_y2 = lines_data$ctrl_y2[i]
        )

        if (lines_data$color_type[i] == "Gradient") {
          n_points <- nrow(bezier_points)
          split_index <- round(gradient_position * n_points)
          color_interpolator <- colorRampPalette(c(start_color, end_color))
          intermediate_color <- color_interpolator(3)[2]

          gradient_colors_start <- colorRampPalette(c(start_color, intermediate_color))(split_index)
          gradient_colors_end <- colorRampPalette(c(intermediate_color, end_color))(n_points - split_index + 1)

          # Add gradient path segments
          for (j in 1:(split_index - 1)) {
            line_layers[[length(line_layers) + 1]] <- annotate(
              "path",
              x = bezier_points$x[j:(j + 1)],
              y = bezier_points$y[j:(j + 1)],
              color = gradient_colors_start[j],
              linewidth = adjusted_line_width,
              alpha = lines_data$alpha[i]
            )
          }

          for (j in split_index:(n_points - 1)) {
            line_layers[[length(line_layers) + 1]] <- annotate(
              "path",
              x = bezier_points$x[j:(j + 1)],
              y = bezier_points$y[j:(j + 1)],
              color = gradient_colors_end[j - split_index + 1],
              linewidth = adjusted_line_width,
              alpha = lines_data$alpha[i]
            )
          }
        } else {
          line_layers[[length(line_layers) + 1]] <- annotate(
            "path",
            x = bezier_points$x,
            y = bezier_points$y,
            color = start_color,
            linewidth = adjusted_line_width,
            alpha = lines_data$alpha[i],
            linetype = lines_data$line_style[i]
          )
        }

        # Add arrowhead for curved lines if needed
        arrow_type <- lines_data$arrow_type[i]
        if (line_type == "Curved Arrow" && !is.null(arrow_type) && !is.na(adjusted_arrow_size)) {
          dx_end <- bezier_points$x[nrow(bezier_points)] - bezier_points$x[nrow(bezier_points) - 1]
          dy_end <- bezier_points$y[nrow(bezier_points)] - bezier_points$y[nrow(bezier_points) - 1]
          norm_end <- sqrt(dx_end^2 + dy_end^2)

          if (lines_data$two_way[i]) {
            dx_start <- bezier_points$x[2] - bezier_points$x[1]
            dy_start <- bezier_points$y[2] - bezier_points$y[1]
            norm_start <- sqrt(dx_start^2 + dy_start^2)

            line_layers[[length(line_layers) + 1]] <- annotate(
              "segment",
              x = bezier_points$x[1], y = bezier_points$y[1],
              xend = bezier_points$x[1] - dx_start / norm_start * 1e-5,
              yend = bezier_points$y[1] - dy_start / norm_start * 1e-5,
              linewidth = adjusted_line_width,
              arrow = arrow(type = arrow_type, length = unit(adjusted_arrow_size, "inches")),
              color = if (lines_data$color_type[i] == "Gradient") gradient_colors_start[1] else start_color
            )
          }

          line_layers[[length(line_layers) + 1]] <- annotate(
            "segment",
            x = bezier_points$x[nrow(bezier_points)], y = bezier_points$y[nrow(bezier_points)],
            xend = bezier_points$x[nrow(bezier_points)] + dx_end / norm_end * 1e-5,
            yend = bezier_points$y[nrow(bezier_points)] + dy_end / norm_end * 1e-5,
            linewidth = adjusted_line_width,
            arrow = arrow(type = arrow_type, length = unit(adjusted_arrow_size, "inches")),
            color = if (lines_data$color_type[i] == "Gradient") gradient_colors_end[length(gradient_colors_end)] else end_color
          )
        }
      }
    }

    return(line_layers)
  })

  # Flatten the list of layers
  unlist(layers, recursive = FALSE)
}

draw_annotations <- function(annotations_data, zoom_level = 1) {
  if (nrow(annotations_data) > 0) {
    annotations_data$color <- sapply(annotations_data$color, valid_hex)
    annotations_data$fill <- sapply(annotations_data$fill, valid_hex)
    annotations_data$alpha <- sapply(annotations_data$alpha, valid_alpha)
    annotations_data$fontface <- sapply(annotations_data$fontface, valid_fontface)
    annotations_data$font <- sapply(annotations_data$font, valid_font)
    annotations_data$math_expression <- sapply(annotations_data$math_expression, valid_logical)
    annotations_data$locked <- sapply(annotations_data$locked, valid_logical)

    if (!"hjust" %in% names(annotations_data)) {
      annotations_data$hjust <- 0.5  # Default center
    }
    if (!"vjust" %in% names(annotations_data)) {
      annotations_data$vjust <- 0.5  # Default middle
    }

    annotations_data$hjust <- sapply(annotations_data$hjust, valid_alpha)
    annotations_data$vjust <- sapply(annotations_data$vjust, valid_alpha)

    layers <- lapply(1:nrow(annotations_data), function(i) {
      annotation_text <- if (annotations_data$math_expression[i]) {
        suppressWarnings(tryCatch(parse(text = annotations_data$text[i]), error = function(e) annotations_data$text[i]))
      } else {
        annotations_data$text[i]
      }

      adjusted_size <- (annotations_data$size[i] / 3) / zoom_level

      # Add annotation to the plot
      annotate("label",
               x = annotations_data$x[i],
               y = annotations_data$y[i],
               label = annotation_text,
               size = adjusted_size,
               color = annotations_data$color[i],
               fill = annotations_data$fill[i],
               alpha = annotations_data$alpha[i],
               angle = annotations_data$angle[i],
               family = annotations_data$font[i],
               fontface = annotations_data$fontface[i],
               hjust = annotations_data$hjust[i],
               vjust = annotations_data$vjust[i],
               linewidth = NA
      )
    })
    return(layers)
  } else {
    return(NULL)
  }
}

draw_loops <- function(loops_data, zoom_level = 1) {
  if (!is.null(loops_data) && nrow(loops_data) > 0) {
    loops_data$color <- sapply(loops_data$color, valid_hex)
    loops_data$alpha <- sapply(loops_data$alpha, valid_alpha)
    loops_data$locked <- sapply(loops_data$locked, valid_logical)

    layers <- lapply(1:nrow(loops_data), function(i) {
      t <- seq(0, 2 * pi, length.out = 100)
      gap_angle <- loops_data$gap_size[i] * pi

      gap_center_angle <- 0  # Gap centered at 0°
      gap_start_angle <- gap_center_angle - (gap_angle / 2)
      gap_end_angle <- gap_center_angle + (gap_angle / 2)

      gap_start_angle <- gap_start_angle %% (2 * pi)
      gap_end_angle <- gap_end_angle %% (2 * pi)

      if (gap_start_angle < gap_end_angle) {
        loop_t <- t[t < gap_start_angle | t > gap_end_angle]
      } else {
        loop_t <- t[t > gap_end_angle & t < gap_start_angle]
      }

      x_ellipse <- loops_data$x_center[i] + (loops_data$loop_width[i]) * loops_data$radius[i] * cos(loop_t)
      y_ellipse <- loops_data$y_center[i] + (loops_data$loop_height[i]) * loops_data$radius[i] * sin(loop_t)

      effective_orientation <- loops_data$orientation[i] + 90

      theta <- effective_orientation * pi / 180
      x_rotated <- cos(theta) * (x_ellipse - loops_data$x_center[i]) - sin(theta) * (y_ellipse - loops_data$y_center[i]) + loops_data$x_center[i]
      y_rotated <- sin(theta) * (x_ellipse - loops_data$x_center[i]) + cos(theta) * (y_ellipse - loops_data$y_center[i]) + loops_data$y_center[i]

      if (loops_data$two_way[i] == FALSE) {
        annotate("path",
                 x = x_rotated,
                 y = y_rotated,
                 color = loops_data$color[i],
                 linewidth = loops_data$width[i] / zoom_level,
                 alpha = loops_data$alpha[i],
                 arrow = arrow(type = loops_data$arrow_type[i],
                               length = unit(loops_data$arrow_size[i] / zoom_level, "inches")))
      } else {
        annotate("path",
                 x = x_rotated,
                 y = y_rotated,
                 color = loops_data$color[i],
                 linewidth = loops_data$width[i] / zoom_level,
                 alpha = loops_data$alpha[i],
                 arrow = arrow(type = loops_data$arrow_type[i],
                               ends = "both",
                               length = unit(loops_data$arrow_size[i] / zoom_level, "inches")))
      }
    })
    return(layers)
  } else {
    return(NULL)
  }
}


adjust_axis_range <- function(plot,
                              x_range = NULL,
                              y_range = NULL,
                              buffer_percent = 0,
                              fixed_aspect_ratio = TRUE,
                              points = NULL,
                              lines = NULL,
                              annotations = NULL,
                              loops = NULL) {

  # If ranges are NULL, compute them from all elements
  if (is.null(x_range) || is.null(y_range)) {
    global_ranges <- compute_global_ranges(
      points = points,
      lines = lines,
      annotations = annotations,
      loops = loops
    )
    current_x_range <- global_ranges$x_range
    current_y_range <- global_ranges$y_range
  } else {
    # Use the existing get_axis_range for the plot
    axis_ranges <- get_axis_range(plot)
    current_x_range <- axis_ranges$x_range
    current_y_range <- axis_ranges$y_range
  }

  new_x_range <- if (!is.null(x_range)) x_range else current_x_range
  new_y_range <- if (!is.null(y_range)) y_range else current_y_range

  x_buffer <- (new_x_range[2] - new_x_range[1]) * (buffer_percent / 100)
  y_buffer <- (new_y_range[2] - new_y_range[1]) * (buffer_percent / 100)

  new_x_range <- c(new_x_range[1] - x_buffer, new_x_range[2] + x_buffer)
  new_y_range <- c(new_y_range[1] - y_buffer, new_y_range[2] + y_buffer)

  if (fixed_aspect_ratio) {
    x_width <- diff(new_x_range)
    y_height <- diff(new_y_range)
    aspect_ratio <- y_height / x_width

    if (aspect_ratio > 1) {
      # Adjust x_range to match aspect ratio
      x_center <- mean(new_x_range)
      new_x_range <- c(x_center - y_height / 2, x_center + y_height / 2)
    } else {
      # Adjust y_range to match aspect ratio
      y_center <- mean(new_y_range)
      new_y_range <- c(y_center - x_width / 2, y_center + x_width / 2)
    }
  }

  adjusted_plot <- plot +
    coord_cartesian(xlim = new_x_range, ylim = new_y_range)

  return(adjusted_plot)
}

compute_global_ranges <- function(points = NULL, lines = NULL, annotations = NULL, loops = NULL) {
  all_x <- numeric()
  all_y <- numeric()

  if (!is.null(points)) {
    all_x <- c(all_x, points$x)
    all_y <- c(all_y, points$y)
  }

  if (!is.null(lines)) {
    all_x <- c(all_x,
               lines$x_start,
               lines$x_end)
    all_y <- c(all_y,
               lines$y_start,
               lines$y_end)
  }

  if (!is.null(annotations)) {
    all_x <- c(all_x, annotations$x)
    all_y <- c(all_y, annotations$y)
  }

  if (!is.null(loops) && nrow(loops) > 0) {
    all_x <- c(all_x, loops$x_center)
    all_y <- c(all_y, loops$y_center)
  }

  if (length(all_x) > 0 && length(all_y) > 0) {
    x_range <- c(min(all_x), max(all_x))
    y_range <- c(min(all_y), max(all_y))

    max_range <- max(x_range[2] - x_range[1], y_range[2] - y_range[1])
    buffer <- max_range * 0.05 # 5%

    x_range <- c(x_range[1] - buffer, x_range[2] + buffer)
    y_range <- c(y_range[1] - buffer, y_range[2] + buffer)

    return(list(x_range = x_range, y_range = y_range))
  } else {
    # Default ranges if no elements exist
    return(list(x_range = c(-10, 10), y_range = c(-10, 10)))
  }
}

get_axis_range <- function(plot) {
  plot_build <- ggplot_build(plot)
  y_range <- plot_build$layout$panel_params[[1]]$y.range
  x_range <- plot_build$layout$panel_params[[1]]$x.range
  res <- list(x_range, y_range)
  names(res) <- c('x_range', 'y_range')
  return(res)
}

save_figure <- function(filename, plot, units = "in", dpi = 300, aspect_ratio = NULL,
                        scale_factor = 0.122, zoom_level = 1, ...) {

  axis_ranges <- get_axis_range(plot)

  x_range <- axis_ranges$x_range
  y_range <- axis_ranges$y_range

  x_span <- diff(x_range)
  y_span <- diff(y_range)

  adjusted_scale_factor <- scale_factor / zoom_level

  # Determine width and height
  if (!is.null(aspect_ratio)) {
    height <- y_span * adjusted_scale_factor
    width <- height * aspect_ratio
  } else {
    width <- x_span * adjusted_scale_factor
    height <- y_span * adjusted_scale_factor
  }

  ggsave(
    filename = filename,
    plot = plot,
    width = width,
    height = height,
    units = units,
    dpi = dpi,
    ...
  )
}

rescale_values <- function(x, to = c(0, 1), from = range(x, na.rm = TRUE)) {
  (x - from[1]) / diff(from) * diff(to) + to[1]
}

safe_which <- function(condition) {
  result <- which(condition)
  if(length(result) > 0) result else NULL
}



to_hex <- function(color) {
  if (grepl("^#[0-9A-Fa-f]{6}$", color, ignore.case = TRUE)) {
    return(color)
  }
  else if (color %in% colors()) {
    rgb_matrix <- col2rgb(color)
    return(rgb(rgb_matrix[1,], rgb_matrix[2,], rgb_matrix[3,], maxColorValue = 255))
  }
  else {
    return(color)
  }
}

create_natural_language_prompt <- function(user_input, data, model_type, existing_variables) {
  numeric_cols <- names(data)[sapply(data, is.numeric)]
  factor_cols <- names(data)[sapply(data, is.factor)]

  model_type_instructions <- switch(
    model_type,
    "cfa" = "Create a confirmatory factor analysis (CFA) model with latent factors.",
    "path" = "Create a path analysis model with directed paths between variables.",
    "sem" = "Create a structural equation model with both measurement and structural components.",
    "growth" = "Create a latent growth curve model."
  )

  prompt <- paste(
    "You are an expert in structural equation modeling (SEM) and the lavaan package in R.",
    "Convert the user's natural language description into valid lavaan syntax.",
    "",
    "MODEL TYPE:", model_type_instructions,
    "",
    "USER'S NATURAL LANGUAGE DESCRIPTION:",
    user_input,
    "",
    "AVAILABLE VARIABLES IN DATASET:",
    paste("- All variables:", paste(existing_variables, collapse = ", ")),
    paste("- Numeric variables (recommended for analysis):", paste(numeric_cols, collapse = ", ")),
    if(length(factor_cols) > 0) paste("- Categorical variables:", paste(factor_cols, collapse = ", ")),
    "",
    "DATASET DIMENSIONS:",
    paste("- Rows:", nrow(data), "- Columns:", ncol(data)),
    "",
    "IMPORTANT INSTRUCTIONS:",
    "1. Generate ONLY valid lavaan syntax",
    "2. Use only the variable names provided above",
    "3. Follow proper lavaan syntax rules:",
    "   - Latent factors: f1 =~ item1 + item2 + item3",
    "   - Regressions: y ~ x1 + x2",
    "   - Variances: x1 ~~ x1",
    "   - Covariances: x1 ~~ x2",
    "   - Intercepts: y ~ 1",
    "4. Include brief comments with # to explain the model structure",
    "5. Make the model theoretically plausible based on the variable names",
    "6. If the user request is unclear, create a simple CFA model using the numeric variables",
    "7. Return ONLY the lavaan syntax, no explanations, no markdown formatting",
    "",
    "EXAMPLE OUTPUT:",
    "# Measurement model",
    "f1 =~ var1 + var2 + var3",
    "f2 =~ var4 + var5 + var6",
    "",
    "# Structural model",
    "f2 ~ f1",
    "",
    "Now generate the lavaan syntax for the user's request:"
  )

  return(prompt)
}


clean_ai_lavaan_response <- function(response) {
  response <- gsub("```.*?```", "", response)  # Remove ```code``` blocks
  response <- gsub("`", "", response)  # Remove inline code markers

  response <- gsub("^.*?lavaan\\s*syntax", "", response, ignore.case = TRUE)
  response <- gsub("^.*?here(?:'s| is)", "", response, ignore.case = TRUE)

  response <- trimws(response)

  response <- gsub("^\\n+|\\n+$", "", response)

  return(response)
}


extract_groups_from_data <- function(df) {
  if (!is.null(df) && "group" %in% names(df)) {
    return(unique(df$group))
  }
  return(character(0))
}

extract_groups_by_condition <- function(values, column_name, condition_value = TRUE) {
  groups <- character(0)

  datasets <- list(values$points, values$lines, values$annotations)

  for (df in datasets) {
    if (!is.null(df) &&
        column_name %in% names(df) &&
        "group" %in% names(df)) {
      filtered_groups <- df |>
        filter(.data[[column_name]] == condition_value) |>
        pull(group) |>
        unique()
      groups <- unique(c(groups, filtered_groups))
    }
  }

  return(groups)
}

capture_complete_workflow <- function(values, include_history = FALSE) {
  all_sem_groups <- list()
  all_network_groups <- list()

  values_group <- unique(c(
    extract_groups_from_data(values$points),
    extract_groups_from_data(values$lines),
    extract_groups_from_data(values$annotations),
    extract_groups_from_data(values$loops)
  ))

  sem_group <- extract_groups_by_condition(values, "lavaan", TRUE)
  network_group <- extract_groups_by_condition(values, "network", TRUE)

  all_groups_to_process <- unique(c(values_group, sem_group, network_group))

  for (group_id in all_groups_to_process) {
    if (group_id %in% sem_group) {
      group_data <- values$group_storage$sem[[group_id]]
      if (!is.null(group_data)) {
        all_sem_groups[[group_id]] <- group_data
      }
    }

    if (group_id %in% network_group) {
      group_data <- values$group_storage$network[[group_id]]
      if (!is.null(group_data)) {
        all_network_groups[[group_id]] <- group_data
      }
    }
  }

  all_sem_modifications <- list()
  all_network_modifications <- list()

  sem_mod_group <- if (!is.null(values$group_storage$modifications)) {
    names(values$group_storage$modifications)
  } else {
    character(0)
  }

  for (group_id in sem_mod_group) {
    mod_data <- values$group_storage$modifications[[group_id]]

    if (!is.null(mod_data)) {
      sem_modifications <- list()
      for (mod_type in names(mod_data)) {
        sem_modifications[[mod_type]] <- mod_data[[mod_type]]
      }

      all_sem_modifications[[group_id]] <- sem_modifications
    }
  }

  network_mod_group <- if (!is.null(values$group_storage$modifications_network)) {
    names(values$group_storage$modifications_network)
  } else {
    character(0)
  }

  for (group_id in network_mod_group) {
    mod_data <- values$group_storage$modifications_network[[group_id]]

    if (!is.null(mod_data)) {
      network_modifications <- list()
      for (mod_type in names(mod_data)) {
        network_modifications[[mod_type]] <- mod_data[[mod_type]]
      }

      all_network_modifications[[group_id]] <- network_modifications
    }
  }



  list(
    sem_groups = all_sem_groups,
    network_groups = all_network_groups,
    modifications = list(
      sem = all_sem_modifications,
      network = all_network_modifications
    ),
    visual_elements = list(
      points = values$points,
      lines = values$lines,
      loops = values$loops,
      annotations = values$annotations
    ),
    metadata = list(
      timestamp = Sys.time(),
      total_groups = length(all_groups_to_process),
      total_sem_modifications = length(all_sem_modifications),
      total_network_modifications = length(all_network_modifications),
      values_groups_count = length(values_group)
    )
  )
}

get_group_differences <- function(fit,
                                  alpha = 0.05,
                                  p_adjust = "none",
                                  test_type = "pairwise",
                                  group1 = "",
                                  group2 = "") {

  group_info <- lavInspect(fit, "group.label")
  group_mapping <- setNames(group_info, 1:length(group_info))

  params <- parameterEstimates(fit) |>
    mutate(group = ifelse(group %in% names(group_mapping),
                          group_mapping[as.character(group)],
                          group))

  pairwise_results <- data.frame()

  multi_group_params <- params |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() >= 2) |>
    ungroup()

  if (test_type == "pairwise") {
    if (group1 != "" && group2 != "") {
      all_groups <- unique(multi_group_params$group)
      if (!group1 %in% all_groups) {
        stop("Group '", group1, "' not found. Available groups: ", paste(all_groups, collapse = ", "))
      }
      if (!group2 %in% all_groups) {
        stop("Group '", group2, "' not found. Available groups: ", paste(all_groups, collapse = ", "))
      }

      group_levels <- c(group1, group2)
    } else {
      # Use all groups if none specified
      group_levels <- unique(multi_group_params$group)
    }

    for (i in 1:(length(group_levels)-1)) {
      for (j in (i+1):length(group_levels)) {
        current_group1 <- group_levels[i]
        current_group2 <- group_levels[j]

        pair_diffs <- multi_group_params |>
          group_by(lhs, op, rhs) |>
          filter(group %in% c(current_group1, current_group2)) |>
          filter(dplyr::n() == 2) |>
          summarise(
            comparison = paste(current_group1, "vs", current_group2),
            p_value = 2 * (1 - pnorm(abs(diff(est)) / sqrt(sum(se^2)))),
            .groups = 'drop'
          )

        pairwise_results <- bind_rows(pairwise_results, pair_diffs)
      }
    }

    # Apply p-value correction
    if (p_adjust != "none") {
      pairwise_results$p_value <- p.adjust(pairwise_results$p_value, method = p_adjust)
    }

    pairwise_results$significant <- pairwise_results$p_value < alpha

    result <- pairwise_results |>
      mutate(
        # Convert to "from" and "to" format
        from = case_when(
          op == "~1" ~ paste0("Intercept_", lhs),
          op == "=~" ~ lhs,
          op == "~~" ~ lhs,
          op == "~"  ~ rhs
        ),
        to = case_when(
          op == "~1" ~ lhs,
          op == "=~" ~ rhs,
          op == "~~" ~ rhs,
          op == "~"  ~ lhs
        ),
        op = op
      ) |>
      select(from, op,  to, comparison, p_value, significant) |>
      arrange(p_value) |>
      rename(lhs = from,
             rhs = to,
             op2 = op) |>
      mutate(op = 'to')  |>
      filter(op2 %in% c("=~", "~1", "~~")) |>
      filter(significant) |>
      select(-op2) |>
      select(lhs, op,  rhs, comparison, p_value, significant)

  }

  return(result)
}

get_bayesian_group_differences <- function(fit,
                                           rope = c(-0.1, 0.1),  # Region of Practical Equivalence
                                           group1 = "",
                                           group2 = "") {

  group_info <- blavaan::blavInspect(fit, "group.label")
  group_mapping <- setNames(group_info, 1:length(group_info))

  # Get parameter estimates and HPD intervals
  params <- lavaan::parameterEstimates(fit)
  hpd_intervals <- as.data.frame(blavaan::blavInspect(fit, "hpd"))

  params$group <- ifelse(params$group %in% names(group_mapping),
                         group_mapping[as.character(params$group)],
                         params$group)

  param_counts <- table(paste(params$lhs, params$op, params$rhs))
  multi_param_names <- names(param_counts)[param_counts >= 2]
  multi_group_params <- params[paste(params$lhs, params$op, params$rhs) %in% multi_param_names, ]

  all_groups <- unique(multi_group_params$group)

  if (group1 != "" && group2 != "") {
    if (!group1 %in% all_groups) {
      stop("Group '", group1, "' not found. Available groups: ", paste(all_groups, collapse = ", "))
    }
    if (!group2 %in% all_groups) {
      stop("Group '", group2, "' not found. Available groups: ", paste(all_groups, collapse = ", "))
    }
    group_levels <- c(group1, group2)
  } else {
    group_levels <- all_groups
  }

  results <- data.frame()

  for (i in 1:(length(group_levels)-1)) {
    for (j in (i+1):length(group_levels)) {
      current_group1 <- group_levels[i]
      current_group2 <- group_levels[j]

      group1_num <- which(group_info == current_group1)
      group2_num <- which(group_info == current_group2)

      # Get unique parameters
      unique_params <- unique(paste(multi_group_params$lhs, multi_group_params$op, multi_group_params$rhs))

      for (param in unique_params) {
        parts <- strsplit(param, " ")[[1]]
        if (length(parts) != 3) next

        lhs_val <- parts[1]
        op_val <- parts[2]
        rhs_val <- parts[3]

        if (!op_val %in% c("=~", "~1", "~~")) next

        param_data <- multi_group_params[
          multi_group_params$lhs == lhs_val &
            multi_group_params$op == op_val &
            multi_group_params$rhs == rhs_val &
            multi_group_params$group %in% c(current_group1, current_group2),
        ]

        if (nrow(param_data) == 2) {
          # Calculate posterior difference
          diff_est <- param_data$est[1] - param_data$est[2]  # Posterior mean difference

          param_name_base <- paste0(lhs_val, op_val, rhs_val)
          param_name1 <- if (group1_num == 1) param_name_base else paste0(param_name_base, ".g", group1_num)
          param_name2 <- if (group2_num == 1) param_name_base else paste0(param_name_base, ".g", group2_num)

          hpd_idx1 <- which(rownames(hpd_intervals) == param_name1)
          hpd_idx2 <- which(rownames(hpd_intervals) == param_name2)

          if (length(hpd_idx1) > 0 && length(hpd_idx2) > 0) {
            hpd1_lower <- hpd_intervals$lower[hpd_idx1]
            hpd1_upper <- hpd_intervals$upper[hpd_idx1]
            hpd2_lower <- hpd_intervals$lower[hpd_idx2]
            hpd2_upper <- hpd_intervals$upper[hpd_idx2]

            diff_lower <- hpd1_lower - hpd2_upper  # Conservative bound
            diff_upper <- hpd1_upper - hpd2_lower  # Conservative bound

            credible_excludes_zero <- diff_lower > 0 | diff_upper < 0
            excludes_rope <- diff_lower > rope[2] | diff_upper < rope[1]

            if (op_val == "~1") {
              from_val <- paste0("Intercept_", lhs_val)
              to_val <- lhs_val
            } else if (op_val == "=~") {
              from_val <- lhs_val
              to_val <- rhs_val
            }

            results <- rbind(results, data.frame(
              lhs = from_val,
              op = "to",
              rhs = to_val,
              comparison = paste(current_group1, "vs", current_group2),
              posterior_mean_diff = diff_est,
              credible_interval = paste0("[", round(diff_lower, 3), ", ", round(diff_upper, 3), "]"),
              excludes_zero = credible_excludes_zero,
              excludes_rope = excludes_rope,
              rope_lower = rope[1],
              rope_upper = rope[2],
              stringsAsFactors = FALSE
            ))
          }
        }
      }
    }
  }

  if (nrow(results) > 0) {
    results <- results[order(abs(results$posterior_mean_diff), decreasing = TRUE), ]
    rownames(results) <- NULL
  }

  return(results)
}

get_est_differences <- function(fit,
                                alpha = 0.05,
                                p_adjust = "none",
                                test_type = "pairwise",
                                group1 = "",
                                group2 = "") {

  group_info <- lavInspect(fit, "group.label")
  group_mapping <- setNames(group_info, 1:length(group_info))

  params <- parameterEstimates(fit) |>
    mutate(group = ifelse(group %in% names(group_mapping),
                          group_mapping[as.character(group)],
                          group))

  params <- params[params$op %in% c("=~", "~1", "~~", "~"), ]

  pairwise_results <- data.frame()

  multi_group_params <- params |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() >= 2) |>
    ungroup()

  if (test_type == "pairwise") {
    if (group1 != "" && group2 != "") {
      all_groups <- unique(multi_group_params$group)
      if (!group1 %in% all_groups) {
        stop("Group '", group1, "' not found. Available groups: ", paste(all_groups, collapse = ", "))
      }
      if (!group2 %in% all_groups) {
        stop("Group '", group2, "' not found. Available groups: ", paste(all_groups, collapse = ", "))
      }

      group_levels <- c(group1, group2)
    } else {
      # Use all groups if none specified
      group_levels <- unique(multi_group_params$group)
    }

    for (i in 1:(length(group_levels)-1)) {
      for (j in (i+1):length(group_levels)) {
        current_group1 <- group_levels[i]
        current_group2 <- group_levels[j]

        pair_diffs <- multi_group_params |>
          group_by(lhs, op, rhs) |>
          filter(group %in% c(current_group1, current_group2)) |>
          filter(dplyr::n() == 2) |>
          summarise(
            comparison = paste(current_group1, "vs", current_group2),
            p_value = 2 * (1 - pnorm(abs(diff(est)) / sqrt(sum(se^2)))),
            .groups = 'drop'
          )


        pairwise_results <- bind_rows(pairwise_results, pair_diffs)
      }
    }
  }

  pairwise_results$significant <- pairwise_results$p_value < alpha

  results <- pairwise_results

  return(results)
}

get_est_differences_bayes <- function(fit,
                                      p_adjust = "none",
                                      test_type = "pairwise",
                                      group1 = "",
                                      group2 = "",
                                      rope = c(-0.1, 0.1)) {

  group_info <- blavaan::blavInspect(fit, "group.label")
  group_mapping <- setNames(group_info, 1:length(group_info))

  # Get parameter estimates and HPD intervals
  params <- lavaan::parameterEstimates(fit)
  hpd_intervals <- as.data.frame(blavaan::blavInspect(fit, "hpd"))

  params$group <- ifelse(params$group %in% names(group_mapping),
                         group_mapping[as.character(params$group)],
                         params$group)

  params <- params[params$op %in% c("=~", "~1", "~~", "~"), ]

  pairwise_results <- data.frame()


  multi_group_params <- params |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() >= 2) |>
    ungroup()

  if (test_type == "pairwise") {
    if (group1 != "" && group2 != "") {
      all_groups <- unique(multi_group_params$group)
      if (!group1 %in% all_groups) {
        stop("Group '", group1, "' not found. Available groups: ", paste(all_groups, collapse = ", "))
      }
      if (!group2 %in% all_groups) {
        stop("Group '", group2, "' not found. Available groups: ", paste(all_groups, collapse = ", "))
      }
      group_levels <- c(group1, group2)
    } else {
      group_levels <- unique(multi_group_params$group)
    }

    for (i in 1:(length(group_levels)-1)) {
      for (j in (i+1):length(group_levels)) {
        current_group1 <- group_levels[i]
        current_group2 <- group_levels[j]

        group1_num <- which(group_info == current_group1)
        group2_num <- which(group_info == current_group2)

        pair_diffs <- multi_group_params |>
          group_by(lhs, op, rhs) |>
          filter(group %in% c(current_group1, current_group2)) |>
          filter(dplyr::n() == 2) |>
          group_map(~ {
            diff_est <- .x$est[1] - .x$est[2]

            # Get parameter names for HPD intervals
            param_name_base <- paste0(.x$lhs[1], .x$op[1], .x$rhs[1])
            param_name1 <- if (group1_num == 1) param_name_base else paste0(param_name_base, ".g", group1_num)
            param_name2 <- if (group2_num == 1) param_name_base else paste0(param_name_base, ".g", group2_num)

            # Get HPD intervals
            hpd_idx1 <- which(rownames(hpd_intervals) == param_name1)
            hpd_idx2 <- which(rownames(hpd_intervals) == param_name2)

            if (length(hpd_idx1) > 0 && length(hpd_idx2) > 0) {
              hpd1_lower <- hpd_intervals$lower[hpd_idx1]
              hpd1_upper <- hpd_intervals$upper[hpd_idx1]
              hpd2_lower <- hpd_intervals$lower[hpd_idx2]
              hpd2_upper <- hpd_intervals$upper[hpd_idx2]

              # Conservative bounds for difference
              diff_lower <- hpd1_lower - hpd2_upper
              diff_upper <- hpd1_upper - hpd2_lower

              # Bayesian significance tests
              credible_excludes_zero <- diff_lower > 0 | diff_upper < 0
              excludes_rope <- diff_lower > rope[2] | diff_upper < rope[1]

              data.frame(
                lhs = .x$lhs[1],
                op = .x$op[1],
                rhs = .x$rhs[1],
                comparison = paste(current_group1, "vs", current_group2),
                posterior_mean_diff = diff_est,
                credible_interval = paste0("[", round(diff_lower, 2), ", ", round(diff_upper, 2), "]"),
                excludes_zero = credible_excludes_zero,
                excludes_rope = excludes_rope,
                rope_lower = rope[1],
                rope_upper = rope[2],
                stringsAsFactors = FALSE
              )
            } else {
              data.frame(
                lhs = .x$lhs[1],
                op = .x$op[1],
                rhs = .x$rhs[1],
                comparison = paste(current_group1, "vs", current_group2),
                posterior_mean_diff = diff_est,
                credible_interval = NA,
                excludes_zero = NA,
                excludes_rope = NA,
                rope_lower = rope[1],
                rope_upper = rope[2],
                stringsAsFactors = FALSE
              )
            }
          }, .keep = TRUE) |>
          bind_rows()

        pairwise_results <- bind_rows(pairwise_results, pair_diffs)
      }
    }
  }

  if (nrow(pairwise_results) > 0) {
    pairwise_results$significant <- pairwise_results$excludes_zero
  }

  results <- pairwise_results
  return(results)
}

get_comparison_table <- function(fit, alpha = 0.05, group1 = "", group2 = "", sep_by = "|") {
  sig_diffs <- get_est_differences(fit = fit, alpha = alpha, group1 = group1, group2 = group2)

  if (nrow(sig_diffs) == 0) {
    return(data.frame())
  }

  params <- get_params_with_group_names(fit)

  # Get standardized solutions
  std_solution <- standardizedSolution(fit)
  std_solution <- std_solution |>
    filter(op %in% c("=~", "~1", "~~")) |>
    mutate(group = ifelse(group %in% 1:length(lavInspect(fit, "group.label")),
                          lavInspect(fit, "group.label")[group],
                          group))

  group1_order <- params |>
    filter(group == group1) |>
    distinct(lhs, op, rhs) |>
    mutate(order = row_number())

  # Get unstandardized comparisons with confidence intervals
  group_comparisons <- params |>
    filter(group %in% c(group1, group2)) |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>
    summarise(
      group1_est = est[group == group1],
      group2_est = est[group == group2],
      group1_ci_lower = ci.lower[group == group1],
      group1_ci_upper = ci.upper[group == group1],
      group2_ci_lower = ci.lower[group == group2],
      group2_ci_upper = ci.upper[group == group2],
      .groups = 'drop'
    )

  # Get standardized comparisons with confidence intervals
  std_comparisons <- std_solution |>
    filter(group %in% c(group1, group2)) |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>
    summarise(
      group1_std = est.std[group == group1],
      group2_std = est.std[group == group2],
      group1_std_ci_lower = ci.lower[group == group1],
      group1_std_ci_upper = ci.upper[group == group1],
      group2_std_ci_lower = ci.lower[group == group2],
      group2_std_ci_upper = ci.upper[group == group2],
      .groups = 'drop'
    )

  comparison_table <- sig_diffs |>
    inner_join(group_comparisons, by = c("lhs", "op", "rhs")) |>
    left_join(std_comparisons, by = c("lhs", "op", "rhs")) |>
    left_join(group1_order, by = c("lhs", "op", "rhs")) |>
    mutate(
      comparison_unstd = paste0(round(group1_est, 2),  sep_by , round(group2_est, 2)),
      comparison_std = paste0(round(group1_std, 2), sep_by, round(group2_std, 2)),
      # Add confidence intervals
      confint_unstd = paste0(
        "[", round(group1_ci_lower, 2), ",", round(group1_ci_upper, 2), "]",
        sep_by,
        "[", round(group2_ci_lower, 2), ",", round(group2_ci_upper, 2), "]"
      ),
      confint_std = paste0(
        "[", round(group1_std_ci_lower, 2), ",", round(group1_std_ci_upper, 2), "]",
        sep_by,
        "[", round(group2_std_ci_lower, 2), ",", round(group2_std_ci_upper, 2), "]"
      )
    ) |>
    arrange(order) |>
    select(lhs, op, rhs, comparison_unstd, comparison_std, confint_unstd, confint_std,
           group1_est, group2_est, group1_std, group2_std,
           group1_ci_lower, group1_ci_upper, group2_ci_lower, group2_ci_upper,
           group1_std_ci_lower, group1_std_ci_upper, group2_std_ci_lower, group2_std_ci_upper,
           p_value, significant) |>
    rename(pvalue = p_value, est = comparison_unstd, std = comparison_std) |>
    distinct(lhs, op, rhs, .keep_all = TRUE)

  return(comparison_table)
}


combine_model_parameters_bayes <- function(fit1, fit2, group1 = "Group1", group2 = "Group2", sep_by = "|", get_std_ci = FALSE) {
  params1 <- lavaan::parameterEstimates(fit1)
  params2 <- lavaan::parameterEstimates(fit2)

  std_sol1 <- lavaan::standardizedSolution(fit1)
  std_sol2 <- lavaan::standardizedSolution(fit2)

  hpd1 <- as.data.frame(blavaan::blavInspect(fit1, "hpd"))
  hpd2 <- as.data.frame(blavaan::blavInspect(fit2, "hpd"))

  params1$group_name <- group1
  params2$group_name <- group2
  std_sol1$group_name <- group1
  std_sol2$group_name <- group2

  combined_params <- rbind(params1, params2)
  combined_std <- rbind(std_sol1, std_sol2)

  combined_params <- combined_params[combined_params$op %in% c("=~", "~~", "~1", "~"), ]
  combined_std <- combined_std[combined_std$op %in% c("=~", "~~", "~1", "~"), ]

  if (get_std_ci) {
    std_hdi1 <- get_standardized_hdi(fit1)
    std_hdi2 <- get_standardized_hdi(fit2)

    std_hdi1$group_name <- group1
    std_hdi2$group_name <- group2

    combined_std_hdi <- rbind(std_hdi1, std_hdi2)
    combined_std_hdi <- combined_std_hdi[combined_std_hdi$op %in% c("=~", "~~", "~1", "~"), ]
  } else {
    combined_std_hdi <- combined_std |>
      select(lhs, op, rhs, group_name) |>
      mutate(
        CI_low = NA_real_,
        CI_high = NA_real_
      )
  }

  create_param_name <- function(lhs, op, rhs) {
    case_when(
      op == "=~" ~ paste0(lhs, "=~", rhs),
      op == "~~" & lhs != rhs ~ paste0(lhs, "~~", rhs),
      op == "~~" & lhs == rhs ~ paste0(lhs, "~~", rhs),
      op == "~1" ~ paste0(lhs, "~1"),
      TRUE ~ paste0(lhs, op, rhs)
    )
  }

  comparison_unstd <- combined_params |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>
    summarise(
      group1_est = est[group_name == group1],
      group2_est = est[group_name == group2],
      .groups = 'drop'
    ) |>
    mutate(
      est = paste0(round(group1_est, 2), sep_by, round(group2_est, 2))
    )

  comparison_unstd <- comparison_unstd |>
    mutate(
      param_name_group1 = create_param_name(lhs, op, rhs),
      param_name_group2 = create_param_name(lhs, op, rhs)
    ) |>
    rowwise() |>
    mutate(
      group1_hpd_lower = ifelse(param_name_group1 %in% rownames(hpd1),
                                hpd1[param_name_group1, "lower"], NA_real_),
      group1_hpd_upper = ifelse(param_name_group1 %in% rownames(hpd1),
                                hpd1[param_name_group1, "upper"], NA_real_),
      group2_hpd_lower = ifelse(param_name_group2 %in% rownames(hpd2),
                                hpd2[param_name_group2, "lower"], NA_real_),
      group2_hpd_upper = ifelse(param_name_group2 %in% rownames(hpd2),
                                hpd2[param_name_group2, "upper"], NA_real_)
    ) |>
    ungroup() |>
    mutate(
      confint_unstd = case_when(
        !is.na(group1_hpd_lower) & !is.na(group2_hpd_lower) ~
          paste0(
            "[", round(group1_hpd_lower, 2), ", ", round(group1_hpd_upper, 2), "]",
            sep_by,
            "[", round(group2_hpd_lower, 2), ", ", round(group2_hpd_upper, 2), "]"
          ),
        !is.na(group1_hpd_lower) & is.na(group2_hpd_lower) ~
          paste0(
            "[", round(group1_hpd_lower, 2), ", ", round(group1_hpd_upper, 2), "]",
            sep_by,
            "[", round(group2_est, 2), ", ", round(group2_est, 2), "]"
          ),
        is.na(group1_hpd_lower) & !is.na(group2_hpd_lower) ~
          paste0(
            "[", round(group1_est, 2), ", ", round(group1_est, 2), "]",
            sep_by,
            "[", round(group2_hpd_lower, 2), ", ", round(group2_hpd_upper, 2), "]"
          ),
        TRUE ~
          paste0(
            "[", round(group1_est, 2), ", ", round(group1_est, 2), "]",
            sep_by,
            "[", round(group2_est, 2), ", ", round(group2_est, 2), "]"
          )
      )
    )

  comparison_std <- combined_std |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>
    summarise(
      group1_std = est.std[group_name == group1],
      group2_std = est.std[group_name == group2],
      .groups = 'drop'
    ) |>
    mutate(
      std = paste0(round(group1_std, 2), sep_by, round(group2_std, 2))
    )

  comparison_std_intervals <- combined_std_hdi |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>
    summarise(
      group1_std_lower = CI_low[group_name == group1],
      group1_std_upper = CI_high[group_name == group1],
      group2_std_lower = CI_low[group_name == group2],
      group2_std_upper = CI_high[group_name == group2],
      .groups = 'drop'
    )

  comparison_table <- comparison_unstd |>
    inner_join(comparison_std, by = c("lhs", "op", "rhs")) |>
    inner_join(comparison_std_intervals, by = c("lhs", "op", "rhs")) |>
    mutate(
      confint_std = if (get_std_ci) {
        case_when(
          !is.na(group1_std_lower) & !is.na(group2_std_lower) ~
            paste0(
              "[", round(group1_std_lower, 2), ", ", round(group1_std_upper, 2), "]",
              sep_by,
              "[", round(group2_std_lower, 2), ", ", round(group2_std_upper, 2), "]"
            ),
          !is.na(group1_std_lower) & is.na(group2_std_lower) ~
            paste0(
              "[", round(group1_std_lower, 2), ", ", round(group1_std_upper, 2), "]",
              sep_by,
              "[", round(group2_std, 2), ", ", round(group2_std, 2), "]"
            ),
          is.na(group1_std_lower) & !is.na(group2_std_lower) ~
            paste0(
              "[", round(group1_std, 2), ", ", round(group1_std, 2), "]",
              sep_by,
              "[", round(group2_std_lower, 2), ", ", round(group2_std_upper, 2), "]"
            ),
          TRUE ~
            paste0(
              "[", round(group1_std, 2), ", ", round(group1_std, 2), "]",
              sep_by,
              "[", round(group2_std, 2), ", ", round(group2_std, 2), "]"
            )
        )
      } else {
        paste0(
          "[", round(group1_std, 2), ", ", round(group1_std, 2), "]",
          sep_by,
          "[", round(group2_std, 2), ", ", round(group2_std, 2), "]"
        )
      }
    ) |>
    select(lhs, op, rhs, est, std, confint_unstd, confint_std)

  return(comparison_table)
}


combine_model_parameters <- function(fit1, fit2, group1 = "Group1", group2 = "Group2", sep_by = "|", ci_level = 0.95) {
  extract_params <- function(fit, target_group) {
    group_info <- lavInspect(fit, "group.label")

    extract_and_label <- function(extract_func) {
      df <- extract_func(fit, ci = TRUE, level = ci_level)

      if (length(group_info) > 0) {
        group_mapping <- setNames(group_info, seq_along(group_info))
        df <- df |>
          mutate(group = ifelse(group %in% names(group_mapping),
                                group_mapping[as.character(group)], group)) |>
          filter(group == target_group)
      } else {
        df$group <- target_group
      }

      df
    }

    list(
      unstd = extract_and_label(parameterEstimates),
      std = extract_and_label(standardizedSolution)
    )
  }

  params1 <- extract_params(fit1, group1)
  params2 <- extract_params(fit2, group2)

  combine_and_filter <- function(sol1, sol2) {
    combined <- bind_rows(sol1, sol2)
    combined[combined$op %in% c("=~", "~~", "~1", "~"), ]
  }

  combined_unstd <- combine_and_filter(params1$unstd, params2$unstd)
  combined_std <- combine_and_filter(params1$std, params2$std)

  create_comparison <- function(combined_df, value_col, round_digits = 2) {
    combined_df |>
      group_by(lhs, op, rhs) |>
      filter(n() == 2) |>
      summarise(
        value = paste0(
          round(.data[[value_col]][group == group1], round_digits),
          sep_by,
          round(.data[[value_col]][group == group2], round_digits)
        ),
        confint = paste0(
          "[", round(ci.lower[group == group1], round_digits), ",",
          round(ci.upper[group == group1], round_digits), "]", sep_by,
          "[", round(ci.lower[group == group2], round_digits), ",",
          round(ci.upper[group == group2], round_digits), "]"
        ),
        .groups = 'drop'
      )
  }

  comparison_unstd <- create_comparison(combined_unstd, "est") |>
    rename(est = value, confint_unstd = confint)

  comparison_std <- create_comparison(combined_std, "est.std") |>
    rename(std = value, confint_std = confint)

  inner_join(comparison_unstd, comparison_std, by = c("lhs", "op", "rhs")) |>
    select(lhs, op, rhs, est, std, confint_unstd, confint_std)
}

get_comparison_table_bayes <- function(fit, rope = c(-0.1, 0.1), group1 = "", group2 = "", sep_by = "|", get_std_ci = FALSE) {
  sig_diffs <- get_est_differences_bayes(fit = fit, rope = rope, group1 = group1, group2 = group2)

  if (nrow(sig_diffs) == 0) {
    return(data.frame())
  }

  group_info <- blavaan::blavInspect(fit, "group.label")
  group_mapping <- setNames(group_info, 1:length(group_info))

  params <- lavaan::parameterEstimates(fit)
  hpd_intervals <- as.data.frame(blavaan::blavInspect(fit, "hpd"))

  params$group <- ifelse(params$group %in% names(group_mapping),
                         group_mapping[as.character(params$group)],
                         params$group)

  params <- params[params$op %in% c("=~", "~1", "~~", "~"), ]

  std_solution <- lavaan::standardizedSolution(fit)
  std_solution <- std_solution |>
    filter(op %in% c("=~", "~1", "~~")) |>
    mutate(group = ifelse(group %in% 1:length(group_info),
                          group_info[group],
                          group))

  if (get_std_ci) {
    std_hdi_intervals <- get_standardized_hdi(fit)
    std_hdi_intervals <- std_hdi_intervals |>
      filter(op %in% c("=~", "~1", "~~")) |>
      mutate(
        group = ifelse(group %in% 1:length(group_info),
                       group_info[group],
                       group)
      )
  } else {
    std_hdi_intervals <- std_solution |>
      select(lhs, op, rhs, group) |>
      mutate(
        est_std = NA_real_,
        CI_low = NA_real_,
        CI_high = NA_real_
      )
  }

  group1_order <- params |>
    filter(group == group1) |>
    distinct(lhs, op, rhs) |>
    mutate(order = row_number())

  group_comparisons <- params |>
    filter(group %in% c(group1, group2)) |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>
    summarise(
      group1_est = est[group == group1],
      group2_est = est[group == group2],
      group1_num = ifelse(group1 %in% group_info, which(group_info == group1), 1),
      group2_num = ifelse(group2 %in% group_info, which(group_info == group2), 2),
      .groups = 'drop'
    )

  group_comparisons <- group_comparisons |>
    rowwise() |>
    mutate(
      param_base = {
        param_row <- params[params$lhs == lhs & params$op == op & params$rhs == rhs &
                              params$group == group1, ]

        if (nrow(param_row) == 0) {
          if (op == "=~") {
            return(paste0(lhs, "=~", rhs))
          } else if (op == "~~") {
            return(paste0(lhs, "..", rhs))
          } else if (op == "~1") {
            return(paste0("Means.", lhs))
          } else {
            return(paste0(lhs, op, rhs))
          }
        }

        if (op == "=~") {
          if (length(param_row$label) > 0 && !is.na(param_row$label[1]) && param_row$label[1] != "") {
            param_row$label[1]
          } else {
            paste0(lhs, "=~", rhs)
          }
        } else if (op == "~~") {
          paste0(lhs, "..", rhs)
        } else if (op == "~1") {
          if (length(param_row$label) > 0 && !is.na(param_row$label[1]) && param_row$label[1] != "") {
            param_row$label[1]
          } else if (lhs %in% c("visual", "textual", "speed") && group1_num == 2) {
            paste0(lhs, ".1.g", group1_num)
          } else {
            paste0("Means.", lhs)  # Fallback
          }
        } else {
          paste0(lhs, op, rhs)
        }
      },

      param_group1 = {
        if (group1_num == 1 && param_base %in% rownames(hpd_intervals)) {
          param_base
        } else {
          patterns <- c(
            paste0(param_base, ".g", group1_num),
            paste0(param_base, "..", group1_num),
            param_base
          )

          result <- param_base
          for (pattern in patterns) {
            if (pattern %in% rownames(hpd_intervals)) {
              result <- pattern
              break
            }
          }
          result
        }
      },

      param_group2 = {
        if (group2_num == 1 && param_base %in% rownames(hpd_intervals)) {
          param_base
        } else {
          patterns <- c(
            paste0(param_base, ".g", group2_num),
            paste0(param_base, "..", group2_num),
            param_base
          )

          result <- param_base
          for (pattern in patterns) {
            if (pattern %in% rownames(hpd_intervals)) {
              result <- pattern
              break
            }
          }
          result
        }
      },

      group1_hpd_lower = ifelse(param_group1 %in% rownames(hpd_intervals),
                                hpd_intervals[param_group1, "lower"], NA_real_),
      group1_hpd_upper = ifelse(param_group1 %in% rownames(hpd_intervals),
                                hpd_intervals[param_group1, "upper"], NA_real_),
      group2_hpd_lower = ifelse(param_group2 %in% rownames(hpd_intervals),
                                hpd_intervals[param_group2, "lower"], NA_real_),
      group2_hpd_upper = ifelse(param_group2 %in% rownames(hpd_intervals),
                                hpd_intervals[param_group2, "upper"], NA_real_)
    ) |>
    ungroup()

  std_comparisons <- std_hdi_intervals |>
    filter(group %in% c(group1, group2)) |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>
    summarise(
      group1_std = est_std[group == group1],
      group2_std = est_std[group == group2],
      group1_std_lower = CI_low[group == group1],
      group1_std_upper = CI_high[group == group1],
      group2_std_lower = CI_low[group == group2],
      group2_std_upper = CI_high[group == group2],
      .groups = 'drop'
    )

  std_point_comparisons <- std_solution |>
    filter(group %in% c(group1, group2)) |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>
    summarise(
      group1_std_point = est.std[group == group1],
      group2_std_point = est.std[group == group2],
      .groups = 'drop'
    )

  std_comparisons <- std_comparisons |>
    left_join(std_point_comparisons, by = c("lhs", "op", "rhs")) |>
    mutate(
      group1_std = ifelse(is.na(group1_std), group1_std_point, group1_std),
      group2_std = ifelse(is.na(group2_std), group2_std_point, group2_std)
    )

  comparison_table <- sig_diffs |>
    inner_join(group_comparisons, by = c("lhs", "op", "rhs")) |>
    left_join(std_comparisons, by = c("lhs", "op", "rhs")) |>
    left_join(group1_order, by = c("lhs", "op", "rhs")) |>
    mutate(
      comparison_unstd = paste0(round(group1_est, 2), sep_by, round(group2_est, 2)),
      comparison_std = paste0(round(group1_std, 2), sep_by, round(group2_std, 2)),
      confint_unstd = case_when(
        !is.na(group1_hpd_lower) & !is.na(group2_hpd_lower) ~
          paste0(
            "[", round(group1_hpd_lower, 2), ", ", round(group1_hpd_upper, 2), "]",
            sep_by,
            "[", round(group2_hpd_lower, 2), ", ", round(group2_hpd_upper, 2), "]"
          ),
        !is.na(group1_hpd_lower) & is.na(group2_hpd_lower) ~
          paste0(
            "[", round(group1_hpd_lower, 2), ", ", round(group1_hpd_upper, 2), "]",
            sep_by,
            "[", round(group2_est, 2), ", ", round(group2_est, 2), "]"
          ),
        is.na(group1_hpd_lower) & !is.na(group2_hpd_lower) ~
          paste0(
            "[", round(group1_est, 2), ", ", round(group1_est, 2), "]",
            sep_by,
            "[", round(group2_hpd_lower, 2), ", ", round(group2_hpd_upper, 2), "]"
          ),
        TRUE ~
          paste0(
            "[", round(group1_est, 2), ", ", round(group1_est, 2), "]",
            sep_by,
            "[", round(group2_est, 2), ", ", round(group2_est, 2), "]"
          )
      ),
      confint_std = if (get_std_ci) {
        case_when(
          !is.na(group1_std_lower) & !is.na(group2_std_lower) ~
            paste0(
              "[", round(group1_std_lower, 2), ", ", round(group1_std_upper, 2), "]",
              sep_by,
              "[", round(group2_std_lower, 2), ", ", round(group2_std_upper, 2), "]"
            ),
          !is.na(group1_std_lower) & is.na(group2_std_lower) ~
            paste0(
              "[", round(group1_std_lower, 2), ", ", round(group1_std_upper, 2), "]",
              sep_by,
              "[", round(group2_std, 2), ", ", round(group2_std, 2), "]"
            ),
          is.na(group1_std_lower) & !is.na(group2_std_lower) ~
            paste0(
              "[", round(group1_std, 2), ", ", round(group1_std, 2), "]",
              sep_by,
              "[", round(group2_std_lower, 2), ", ", round(group2_std_upper, 2), "]"
            ),
          TRUE ~
            paste0(
              "[", round(group1_std, 2), ", ", round(group1_std, 2), "]",
              sep_by,
              "[", round(group2_std, 2), ", ", round(group2_std, 2), "]"
            )
        )
      } else {
        paste0(
          "[", round(group1_std, 2), ", ", round(group1_std, 2), "]",
          sep_by,
          "[", round(group2_std, 2), ", ", round(group2_std, 2), "]"
        )
      }
    ) |>
    arrange(order) |>
    select(lhs, op, rhs, comparison_unstd, comparison_std,
           confint_unstd, confint_std,
           group1_est, group2_est, group1_std, group2_std,
           posterior_mean_diff, credible_interval, excludes_zero, excludes_rope,
           rope_lower, rope_upper) |>
    rename(est = comparison_unstd, std = comparison_std, significant = excludes_zero) |>
    distinct(lhs, op, rhs, .keep_all = TRUE)

  return(comparison_table)
}

get_params_with_group_names <- function(fit) {
  group_info <- lavInspect(fit, "group.label")
  group_mapping <- setNames(group_info, 1:length(group_info))

  params <- parameterEstimates(fit) |>
    filter(op %in% c("=~", "~1", "~~")) |>
    mutate(group = ifelse(group %in% names(group_mapping),
                          group_mapping[as.character(group)],
                          group))
  return(params)
}

get_vars_from_lavaan <- function(fit) {
  all_vars <- lavaan::lavNames(fit)
  latent_vars <- lavaan::lavNames(fit, type = "lv")
  observed_vars <- lavaan::lavNames(fit, type = "ov")

  return(list(
    all = sort(all_vars),
    latent = sort(latent_vars),
    observed = sort(observed_vars)
  ))
}


extract_latent_from_fit <- function(lavaan_fit) {
  if (is.null(lavaan_fit)) return(character(0))

  tryCatch({
    param_table <- lavaan::parameterTable(lavaan_fit)

    lhs_vars <- unique(param_table$lhs[param_table$op == "=~"])
    rhs_vars <- unique(param_table$rhs[param_table$op == "=~"])

    latent_vars <- setdiff(lhs_vars, rhs_vars)

    return(latent_vars)
  }, error = function(e) {
    return(character(0))
  })
}

get_observed_variables <- function(latent_var, lavaan_fit) {
  if (is.null(lavaan_fit) || is.null(latent_var)) return(character(0))

  tryCatch({
    param_table <- lavaan::parameterTable(lavaan_fit)

    observed_vars <- param_table$rhs[param_table$op == "=~" & param_table$lhs == latent_var]

    return(unique(observed_vars))
  }, error = function(e) {
    return(character(0))
  })
}

get_intercept_variables <- function(latent_var, lavaan_fit) {
  if (is.null(lavaan_fit)) return(character(0))

  tryCatch({
    param_table <- lavaan::parameterTable(lavaan_fit)

    latent_intercept <- latent_var[latent_var %in% param_table$lhs[param_table$op == "~1"]]
    observed_vars <- param_table$rhs[param_table$op == "=~" & param_table$lhs == latent_var]
    observed_intercepts <- observed_vars[observed_vars %in% param_table$lhs[param_table$op == "~1"]]
    all_intercepts <- unique(c(latent_intercept, observed_intercepts))
    intercept_names <- paste0("Intercept_", all_intercepts)
    return(intercept_names)

  }, error = function(e) {
    return(character(0))
  })
}

generate_graph_from_qgraph <- function(network_object,
                                       directed = FALSE,
                                       layout_width = 30, layout_height = 30,
                                       x_center = 0, y_center = 0,
                                       node_shape = 'circle',
                                       node_size = 10,
                                       node_alpha = 1,
                                       node_fill_color = "#1262b3",
                                       node_border_color = "#0f993d", node_border_width = 1,
                                       node_width_height_ratio = 1,
                                       line_width = 1, line_color = "#000000",
                                       line_alpha = 1,
                                       min_edge_width = 0.5, max_edge_width = 3, scale_by_weight = FALSE,
                                       line_endpoint_spacing = 0,
                                       arrow_type = "closed",
                                       arrow_size = 0.1,
                                       node_label_font = "sans", node_label_size = 15,
                                       node_label_color = "#000000",
                                       node_label_alpha = 1, node_label_fontface = "plain",
                                       edge_label_font = "sans", edge_label_size = 15,
                                       edge_label_color = "#000000",
                                       edge_label_fill = "#FFFFFF",
                                       edge_label_alpha = 1, edge_label_fontface = "plain",
                                       zoom_factor = 1.2,
                                       data_file = NULL,
                                       bezier_network_edges = FALSE,
                                       network_edges_curvature_magnitude = 0.3,
                                       network_edges_rotate_curvature = FALSE,
                                       network_edges_curvature_asymmetry = 0,
                                       use_clustering = FALSE,
                                       clustering_method = NULL,
                                       cluster_palette = NULL,
                                       modify_params_edge = FALSE,
                                       modified_edges = NULL,
                                       modify_params_edgelabel = FALSE,
                                       modified_edgelabels = NULL,
                                       modify_params_edgelabel_xy = FALSE,
                                       modified_edgelabels_xy = NULL,
                                       modify_params_node = FALSE,
                                       modified_nodes = NULL,
                                       modify_params_node_xy = FALSE,
                                       modified_nodes_xy = NULL,
                                       modify_params_edge_xy = FALSE,
                                       modified_edges_xy = NULL,
                                       modify_params_cov_edge = FALSE,
                                       modified_cov_edges = NULL,
                                       modify_params_nodelabel = FALSE,
                                       modified_nodelabels = NULL,
                                       modify_params_nodelabel_xy = FALSE,
                                       modify_params_bezier_edge = FALSE,
                                       modified_bezier_edges = NULL,
                                       modified_nodelabels_xy = NULL,
                                       modify_params_nodelabel_text = FALSE,
                                       modified_nodelabels_text = NULL,
                                       modify_params_edgelabel_text = FALSE,
                                       modified_edgelabels_text = NULL,
                                       apply_global_nodes = FALSE,
                                       apply_global_edges = FALSE,
                                       apply_global_annotations = FALSE,
                                       flip_layout = FALSE,
                                       flip_direction = NULL,
                                       rotate_layout = FALSE,
                                       rotate_angle = 0,
                                       which_group = "1") {

  apply_modifications <- function(data, modifications, config, mode) {
    if (is.null(modifications) || nrow(modifications) == 0) return(data)
    modified_data <- data

    for (i in seq_len(nrow(modifications))) {
      mod <- modifications[i, ]

      if (mode == 'edge') {
        idx <- which(
          edges_from == mod$lhs &
            edges_to == mod$rhs
        )
      } else if (mode == 'node') {
        idx <- which(
          node_coords$name == mod$text
        )
      }

      if (length(idx) == 1) {
        for (col in config$modify_cols) {
          if (col %in% names(mod) && col %in% names(modified_data)) {
            modified_data[idx, col] <- mod[[col]]
          }
        }

        if (!is.null(config$special_case)) {
          modified_data <- config$special_case(modified_data, idx, mod)
        }
      }
    }
    return(modified_data)
  }

  if (inherits(network_object, "qgraph")) {
    qgraph_obj <- network_object
    node_names <- names(qgraph_obj$graphAttributes$Nodes$labels)
    if (is.null(node_names)) node_names <- as.character(qgraph_obj$graphAttributes$Nodes$labels)

    # Extract and normalize node coordinates
    node_coords <- as.data.frame(qgraph_obj$layout)
    colnames(node_coords) <- c("x", "y")
    node_coords$x <- (node_coords$x - mean(range(node_coords$x))) * layout_width + x_center
    node_coords$y <- (node_coords$y - mean(range(node_coords$y))) * layout_height + y_center
    node_coords$name <- node_names


    # Process edges
    edges_df0 <- data.frame(
      from = qgraph_obj$Edgelist$from,
      to = qgraph_obj$Edgelist$to,
      weight = qgraph_obj$Edgelist$weight,
      directed = qgraph_obj$Edgelist$directed,
      bidirectional = qgraph_obj$Edgelist$bidirectional,
      labels = qgraph_obj$graphAttributes$Edges$labels
    )

    keep_indices <- which(qgraph_obj$graphAttributes$Edges$color != "#00000000")
    edges_df0 <- edges_df0[keep_indices, ]

    edges_df <- edges_df0[!duplicated(
      t(apply(edges_df0[c("from", "to")], 1, sort))
    ), ]

    edges_df <- edges_df[edges_df$from != edges_df$to, ]

    # Edge properties
    edge_op <- ifelse(edges_df$bidirectional, "~~", "~")
    edges_from <- node_names[edges_df$from]
    edges_to <- node_names[edges_df$to]
    edge_labels <- edges_df$labels

  } else {

    stop("Must be output from 'qgraph'.")

  }

  if (use_clustering) {
    num_clusters <- NULL

    n_nodes <- length(node_names)
    adj_mat <- matrix(0, nrow = n_nodes, ncol = n_nodes)

    for (i in seq_len(nrow(edges_df))) {
      from <- edges_df$from[i]
      to <- edges_df$to[i]
      weight <- edges_df$weight[i]
      adj_mat[from, to] <- weight
      adj_mat[to, from] <- weight  # Undirected
    }

    igraph_obj <- igraph::graph_from_adjacency_matrix(adj_mat,
                                                      weighted = TRUE,
                                                      mode = "undirected")

    edge_weights <- E(igraph_obj)$weight

    if (any(edge_weights < 0, na.rm = TRUE)) {
      showNotification(
        paste("Warning: Network contains", sum(edge_weights < 0, na.rm = TRUE),
              "negative edge weights. Taking absolute values for clustering."),
        type = "warning",
        duration = 5
      )
      E(igraph_obj)$weight <- abs(edge_weights)
    }

    if (any(edge_weights == 0, na.rm = TRUE)) {
      zero_weights <- which(edge_weights == 0)
      E(igraph_obj)$weight[zero_weights] <- 0.0001
    }

    if (!is_connected(igraph_obj)) {
      showNotification(
        "Network is not fully connected. Clustering results may be fragmented.",
        type = "warning",
        duration = 5
      )
    }

    nodes <- qgraph_obj$graphAttributes$Nodes

    communities <- tryCatch({
      switch(
        clustering_method,
        "louvain" = cluster_louvain(igraph_obj, weights = E(igraph_obj)$weight),
        "leiden" = cluster_leiden(igraph_obj, weights = E(igraph_obj)$weight),
        "walktrap" = cluster_walktrap(igraph_obj, weights = E(igraph_obj)$weight),
        "fast_greedy" = cluster_fast_greedy(igraph_obj, weights = E(igraph_obj)$weight)
      )
    }, error = function(e) {
      showNotification(
        paste("Clustering failed:", e$message, "Using fallback clustering."),
        type = "warning",
        duration = 5
      )

      tryCatch({
        cluster_spinglass(igraph_obj, weights = abs(E(igraph_obj)$weight))
      }, error = function(e2) {
        components(igraph_obj)$membership
      })
    })

    if (!is.null(communities)) {
      if (inherits(communities, "communities")) {
        nodes$community <- membership(communities)
        num_clusters <- max(nodes$community, na.rm = TRUE)
      } else if (is.numeric(communities) || is.integer(communities)) {
        nodes$community <- communities
        num_clusters <- max(communities, na.rm = TRUE)
      } else {
        nodes$community <- rep(1, vcount(igraph_obj))
        num_clusters <- 1
      }
    } else {
      nodes$community <- rep(1, vcount(igraph_obj))
      num_clusters <- 1
    }

    if (is.na(num_clusters) || num_clusters <= 0) {
      num_clusters <- 1
      nodes$community <- rep(1, length(nodes$community))
    }

    palette_max_colors <- list(
      rainbow = Inf,
      Set3 = 12,
      Paired = 12,
      Dark2 = 8,
      Accent = 8,
      Pastel1 = 9,
      Pastel2 = 8,
      Spectral = 11,
      YlGnBu = 9,
      RdYlBu = 11,
      smplot2 = 20
    )

    palette_function <- switch(
      cluster_palette,
      "rainbow" = function(n) rainbow(n),
      "Set3" = function(n) RColorBrewer::brewer.pal(min(n, 12), "Set3"),
      "Paired" = function(n) RColorBrewer::brewer.pal(min(n, 12), "Paired"),
      "Dark2" = function(n) RColorBrewer::brewer.pal(min(n, 8), "Dark2"),
      "Accent" = function(n) RColorBrewer::brewer.pal(min(n, 8), "Accent"),
      "Pastel1" = function(n) RColorBrewer::brewer.pal(min(n, 9), "Pastel1"),
      "Pastel2" = function(n) RColorBrewer::brewer.pal(min(n, 8), "Pastel2"),
      "Spectral" = function(n) RColorBrewer::brewer.pal(min(n, 11), "Spectral"),
      "YlGnBu" = function(n) RColorBrewer::brewer.pal(min(n, 9), "YlGnBu"),
      "RdYlBu" = function(n) RColorBrewer::brewer.pal(min(n, 11), "RdYlBu"),
      "smplot2" = function(n) head(smplot2::sm_palette(), n)
    )

    max_colors <- palette_max_colors[[cluster_palette]]

    if (cluster_palette != "rainbow" && num_clusters > max_colors) {
      showNotification(
        paste(cluster_palette, "supports a maximum of", max_colors, "colors. Falling back to Rainbow palette."),
        type = "warning",
        duration = 5
      )
      palette_function <- rainbow
    }

    valid_communities <- nodes$community
    if (any(is.na(valid_communities))) {
      valid_communities[is.na(valid_communities)] <- 1
    }
    if (any(valid_communities <= 0)) {
      valid_communities[valid_communities <= 0] <- 1
    }
    if (any(valid_communities > num_clusters)) {
      valid_communities[valid_communities > num_clusters] <- num_clusters
    }

    node_colors <- palette_function(num_clusters)[valid_communities]

  } else {
    node_colors <- node_fill_color
  }


  if (flip_layout) {
    flipped <- flip_around_center(node_coords, flip_direction)
    node_coords$x <- flipped$x
    node_coords$y <- flipped$y
  }

  if (rotate_layout) {
    rotated <- rotate_around_center(node_coords, rotate_angle)
    node_coords$x <- rotated$x
    node_coords$y <- rotated$y
  }

  # Apply node position modifications
  if (modify_params_node_xy) {
    node_coords <- apply_modifications(
      node_coords,
      modified_nodes_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0), # No direct column mods
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )

  }

  # Create points dataframe
  points_df <- data.frame(
    x = node_coords$x,
    y = node_coords$y,
    shape = if (apply_global_nodes) node_shape else qgraph_obj$graphAttributes$Nodes$shape,
    color = if (apply_global_nodes) node_colors else sapply(qgraph_obj$graphAttributes$Nodes$color, to_hex2),
    size = node_size,
    border_color = if (apply_global_nodes) node_border_color else qgraph_obj$graphAttributes$Nodes$border.color,
    border_width = node_border_width,
    alpha = if (apply_global_nodes) node_alpha else sapply(sapply(qgraph_obj$graphAttributes$Nodes$color, to_hex), get_alpha),
    width_height_ratio = if (apply_global_nodes) node_width_height_ratio else qgraph_obj$graphAttributes$Nodes$width / qgraph_obj$graphAttributes$Nodes$height,
    orientation = 0,
    lavaan = FALSE,
    network = TRUE,
    locked = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  #Apply node modifications
  if (modify_params_node) {
    points_df <- apply_modifications(
      points_df,
      modified_nodes,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "alpha", "shape", "size", "border_color", "border_width", "width_height_ratio"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }


  # Create annotations
  annotations <- data.frame(
    text = node_coords$name,
    x = node_coords$x,
    y = node_coords$y,
    font = node_label_font,
    size = node_label_size,
    color = if (apply_global_annotations) node_label_color else sapply(qgraph_obj$graphAttributes$Nodes$label.color, to_hex2),
    fill = NA,
    angle = 0,
    alpha = 1,
    fontface = 'plain',
    math_expression = FALSE,
    hjust = 0.5,
    vjust = 0.5,
    lavaan = FALSE,
    network = TRUE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  if (modify_params_nodelabel) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "size", "alpha", "angle", "font", "fontface"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }

  if (modify_params_nodelabel_xy) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }


  if (modify_params_nodelabel_text) {
    if (nrow(modified_nodelabels_text) > 0) {
      for (i in seq_len(nrow(modified_nodelabels_text))) {
        mod <- modified_nodelabels_text[i, ]
        node_idx <- which(
          node_coords$name == mod$text
        )
        if (length(node_idx) == 1) {
          annotations$text[[node_idx]] <- mod$nodelabel
          annotations$math_expression[[node_idx]] <- mod$math_expression
        }
      }
    }
  }


  if (length(edges_from) == 0 || length(edges_to) == 0) {
    stop("No edges found in the model. Check the Lavaan syntax.")
  }

  # Create lines dataframe
  lines_df_pre <- data.frame(
    x_start = node_coords[match(edges_from, node_names), "x"],
    y_start = node_coords[match(edges_from, node_names), "y"],
    x_end = node_coords[match(edges_to, node_names), "x"],
    y_end = node_coords[match(edges_to, node_names), "y"],
    ctrl_x = NA,
    ctrl_y = NA,
    ctrl_x2 = NA,
    ctrl_y2 = NA,
    curvature_magnitude = NA,
    rotate_curvature = NA,
    curvature_asymmetry = NA,
    type = ifelse(bezier_network_edges == TRUE,
                  ifelse(edges_df$directed, "Curved Arrow", "Curved Line"),
                  ifelse(edges_df$directed,
                         ifelse(edge_op == "~~", "Curved Arrow", "Straight Arrow"),
                         ifelse(edge_op == "~~", "Curved Line", "Straight Line"))),
    color = if (apply_global_edges) line_color else sapply(qgraph_obj$graphAttributes$Edges$color[as.numeric(rownames(edges_df))], to_hex2),
    end_color = NA,
    color_type = "Single",
    gradient_position = NA,
    width = if (apply_global_edges) line_width else qgraph_obj$graphAttributes$Edges$width[as.numeric(rownames(edges_df))] * 0.6,
    alpha = if (apply_global_edges) line_alpha else sapply(sapply(qgraph_obj$graphAttributes$Edges$color[as.numeric(rownames(edges_df))], to_hex), get_alpha),
    arrow = if (apply_global_edges) ifelse(directed, TRUE, NA) else ifelse(edges_df$directed, TRUE, NA),
    arrow_type = arrow_type,
    arrow_size = arrow_size,
    two_way = edge_op == "~~",
    lavaan = FALSE,
    network = TRUE,
    line_style = "solid",
    locked = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )


  # Apply edge modifications
  if (modify_params_edge) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      modified_edges,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "width", "alpha", "line_style", "end_color", "gradient_position", "color_type")
      ),
      mode = 'edge'
    )
  }

  # Adjust edge coordinates
  edge_list <- cbind(match(edges_from, node_names), match(edges_to, node_names))
  lines <- adjust_edge_coordinates(
    lines_df = lines_df_pre,
    edge_list = edge_list,
    points_df = points_df,
    auto_endpoint_spacing = if (apply_global_edges) line_endpoint_spacing else 0
  )

  if (bezier_network_edges == TRUE) {
    bezier_indices <- which(lines$type %in% c('Curved Arrow', 'Curved Line'))

    control_points <- mapply(
      calculate_control_point,
      x_start = lines$x_start[bezier_indices],
      y_start = lines$y_start[bezier_indices],
      x_end = lines$x_end[bezier_indices],
      y_end = lines$y_end[bezier_indices],
      curvature_magnitude = network_edges_curvature_magnitude,
      rotate_curvature = network_edges_rotate_curvature,
      curvature_asymmetry = network_edges_curvature_asymmetry,
      center_x = mean(node_coords$x),
      center_y = mean(node_coords$y),
      SIMPLIFY = FALSE
    )


    # Assign the calculated control points to lines
    lines$ctrl_x[bezier_indices] <- sapply(control_points, `[[`, "ctrl_x")
    lines$ctrl_y[bezier_indices] <- sapply(control_points, `[[`, "ctrl_y")
    lines$ctrl_x2[bezier_indices] <- sapply(control_points, `[[`, "ctrl_x2")
    lines$ctrl_y2[bezier_indices] <- sapply(control_points, `[[`, "ctrl_y2")
    lines$curvature_magnitude[bezier_indices] <- network_edges_curvature_magnitude
    lines$rotate_curvature[bezier_indices] <- network_edges_rotate_curvature
    lines$curvature_asymmetry[bezier_indices] <- network_edges_curvature_asymmetry
    lines$locked[bezier_indices] <- FALSE
  }


  if (modify_params_bezier_edge) {
    lines <- apply_modifications(
      lines,
      modified_bezier_edges,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = NULL,
        special_case = function(data, idx, mod) {
          cp <- calculate_control_point(
            x_start = data$x_start[idx],
            y_start = data$y_start[idx],
            x_end = data$x_end[idx],
            y_end = data$y_end[idx],
            curvature_magnitude = mod$curvature_magnitude,
            rotate_curvature = mod$rotate_curvature,
            curvature_asymmetry = mod$curvature_asymmetry,
            center_x = mean(node_coords$x),
            center_y = mean(node_coords$y)
          )

          # Safely assign control points
          if (all(c("ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2","locked") %in% names(data))) {
            data$ctrl_x[idx] <- cp$ctrl_x
            data$ctrl_y[idx] <- cp$ctrl_y
            data$ctrl_x2[idx] <- cp$ctrl_x2
            data$ctrl_y2[idx] <- cp$ctrl_y2
            data$curvature_magnitude[idx] <- mod$curvature_magnitude
            data$rotate_curvature[idx] <- mod$rotate_curvature
            data$curvature_asymmetry[idx] <- mod$curvature_asymmetry
            data$locked[idx] <- FALSE
            data$type[idx] <- ifelse (directed, 'Curved Arrow', 'Curved Line')
          }
          # }
          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  lines$type[lines$curvature_magnitude == 0] <- "Straight Arrow"
  lines$type[lines$curvature_magnitude != 0] <- "Curved Arrow"


  if (modify_params_edge_xy) {
    lines <- apply_modifications(
      lines,
      modified_edges_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x_start[idx] <- mod$start_x_shift # data$x_start[idx] + mod$start_x_shift
          data$y_start[idx] <- mod$start_y_shift # data$y_start[idx] + mod$start_y_shift
          data$x_end[idx] <- mod$end_x_shift # data$x_end[idx] + mod$end_x_shift
          data$y_end[idx] <- mod$end_y_shift # data$y_end[idx] + mod$end_y_shift

          if (data$type[idx] %in% c('Curved Line', 'Curved Arrow')) {
            cp <- calculate_control_point(
              x_start = data$x_start[idx],
              y_start = data$y_start[idx],
              x_end = data$x_end[idx],
              y_end = data$y_end[idx],
              curvature_magnitude = data$curvature_magnitude[idx],
              rotate_curvature = data$rotate_curvature[idx],
              curvature_asymmetry = data$curvature_asymmetry[idx],
              center_x = mean(node_coords$x),
              center_y = mean(node_coords$y)
            )

            data$ctrl_x[idx] <- cp$ctrl_x
            data$ctrl_y[idx] <- cp$ctrl_y
            data$ctrl_x2[idx] <- cp$ctrl_x2
            data$ctrl_y2[idx] <- cp$ctrl_y2
          }

          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  # Prepare edge labels
  lines_df0 <- cbind(lines, from = edges_from, to = edges_to, text = edge_labels)
  edgelabels_xy_df <- data.frame(x = numeric(nrow(lines_df0)), y = numeric(nrow(lines_df0)))

  for (i in seq_len(nrow(lines_df0))) {
    intp_points <- if (lines_df0$type[i] == "Curved Arrow") {
      create_bezier_curve(
        x_start = lines_df0$x_start[i], y_start = lines_df0$y_start[i],
        x_end = lines_df0$x_end[i], y_end = lines_df0$y_end[i],
        ctrl_x = lines_df0$ctrl_x[i], ctrl_y = lines_df0$ctrl_y[i],
        ctrl_x2 = lines_df0$ctrl_x2[i], ctrl_y2 = lines_df0$ctrl_y2[i], n_points = 100
      )
    } else {
      interpolate_points(
        x_start = lines_df0$x_start[i], y_start = lines_df0$y_start[i],
        x_end = lines_df0$x_end[i], y_end = lines_df0$y_end[i], n = 100
      )
    }
    mid_idx <- 50
    edgelabels_xy_df[i, ] <- intp_points[mid_idx, c("x", "y")]
  }

  label_coords <- data.frame(
    text = lines_df0$text,
    x = edgelabels_xy_df$x,
    y = edgelabels_xy_df$y,
    font = edge_label_font,
    size = edge_label_size,
    color = if (apply_global_annotations) edge_label_color else sapply(qgraph_obj$graphAttributes$Edges$color[as.numeric(rownames(edges_df))], to_hex2),
    fill = edge_label_fill,
    angle = 0,
    alpha = edge_label_alpha,
    fontface = edge_label_fontface,
    math_expression = FALSE,
    hjust = 0.5,
    vjust = 0.5,
    lavaan = FALSE,
    network = TRUE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  ) |>
    filter(nzchar(trimws(text)))

  # Apply edge label modifications
  if (modify_params_edgelabel) {
    label_coords <- apply_modifications(
      label_coords,
      modified_edgelabels,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fill", "size", "alpha", "angle", "font", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (modify_params_edgelabel_xy) {
    label_coords <- apply_modifications(
      label_coords,
      modified_edgelabels_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  if (modify_params_edgelabel_text) {
    if (nrow(modified_edgelabels_text) > 0) {
      for (i in seq_len(nrow(modified_edgelabels_text))) {
        mod <- modified_edgelabels_text[i, ]
        edge_idx <- which(
          edges_from == mod$lhs &
            edges_to == mod$rhs
        )
        if (length(edge_idx) == 1) {
          label_coords$text[[edge_idx]] <- mod$text
          label_coords$math_expression[[edge_idx]] <- mod$math_expression
        }
      }
    }
  }


  annotations <- rbind(annotations, label_coords)

  points_df[c("x", "y")] <- lapply(points_df[c("x", "y")], round, 5)
  line_cols <- c("x_start", "y_start", "x_end", "y_end", "ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2")
  lines[line_cols] <- lapply(lines[line_cols], round, 5)
  annotations[c("x", "y")] <- lapply(annotations[c("x", "y")], round, 5)

  list(points = points_df, lines = lines, annotations = annotations)
}


update_tidysem_labels <- function(tidysem_obj, standardized = FALSE, unstandardized = TRUE,
                                  p_val = TRUE, conf_int = FALSE) {

  create_label <- function(row, standardized, unstandardized, p_val, conf_int) {
    # Check which columns exist
    has_std <- all(c("est_sig_std", "est_std", "confint_std") %in% names(row))
    has_unstd <- all(c("est_sig", "est", "confint") %in% names(row))

    if (standardized == FALSE & unstandardized == FALSE) {
      if (conf_int & has_unstd) {
        return(ifelse(!is.na(row$confint), row$confint, ""))
      } else {
        return("")
      }
    }

    if (standardized == TRUE & !has_std) {
      # Fall back to unstandardized if available
      if (unstandardized == FALSE) {
        unstandardized <- TRUE
      }
      standardized <- FALSE
    }

    if (standardized == TRUE & unstandardized == TRUE) {
      if (p_val & conf_int) {
        return(paste0(row$est_sig, " (", row$est_sig_std, ")\n", row$confint))
      } else if (p_val & !conf_int) {
        return(paste0(row$est_sig, " (", row$est_sig_std, ")"))
      } else if (!p_val & conf_int) {
        return(paste0(row$est, " (", row$est_std, ")\n", row$confint))
      } else {
        return(paste0(row$est, " (", row$est_std, ")"))
      }
    }

    if (standardized == TRUE & unstandardized == FALSE) {
      if (p_val & conf_int) {
        return(paste0(row$est_sig_std, "\n", row$confint_std))
      } else if (p_val & !conf_int) {
        return(row$est_sig_std)
      } else if (!p_val & conf_int) {
        return(paste0(row$est_std, "\n", row$confint_std))
      } else {
        return(as.character(row$est_std))
      }
    }

    if (standardized == FALSE & unstandardized == TRUE) {
      if (p_val & conf_int) {
        return(paste0(row$est_sig, "\n", row$confint))
      } else if (p_val & !conf_int) {
        return(row$est_sig)
      } else if (!p_val & conf_int) {
        return(paste0(row$est, "\n", row$confint))
      } else {
        return(as.character(row$est))
      }
    }

    return("")
  }

  if ("edges" %in% names(tidysem_obj)) {
    tidysem_obj$edges$label <- apply(tidysem_obj$edges, 1, function(row) {
      create_label(as.list(row), standardized, unstandardized, p_val, conf_int)
    })
  }

  if ("nodes" %in% names(tidysem_obj) && !is.null(tidysem_obj$nodes)) {
    if (nrow(tidysem_obj$nodes) > 0 && all(c("est_sig", "est", "confint") %in% names(tidysem_obj$nodes))) {
      tidysem_obj$nodes$label <- apply(tidysem_obj$nodes, 1, function(row) {
        create_label(as.list(row), standardized, unstandardized, p_val, conf_int)
      })
    }
  }

  return(tidysem_obj)
}

update_tidysem_labels_bayes <- function(tidysem_obj, blavaan_fit,
                                        standardized = FALSE, unstandardized = TRUE,
                                        p_val = FALSE, conf_int = FALSE,
                                        ci_level = 0.95) {

  if ((standardized == TRUE && unstandardized == FALSE && conf_int == TRUE)) {
    get_std_ci <- TRUE
  } else {
    get_std_ci <- FALSE
  }

  hpd_intervals <- as.data.frame(blavaan::blavInspect(blavaan_fit, "hpd", level = ci_level))
  hpd_intervals$parameter <- rownames(hpd_intervals)

  std_hdi_intervals <- NULL

  if (get_std_ci) {
    std_hdi_intervals <- get_standardized_hdi(blavaan_fit, ci_level = ci_level)
  }

  group_info <- blavaan::blavInspect(blavaan_fit, "group.label")

  convert_to_hpd_name <- function(lhs, op, rhs, group, label = NULL) {
    group_num <- match(group, group_info)
    if (is.na(group_num)) group_num <- 1

    if (!is.null(label) && label != "" && grepl("^\\.p[0-9]+\\.$", label)) {
      base_name <- label

      if (group_num == 1) {
        return(base_name)
      } else {
        possible_names <- c(
          paste0(base_name, "..", group_num),  # .p2..1
          paste0(base_name, ".g", group_num),  # .p2.g2
          base_name  # Also try without suffix as fallback
        )
        return(possible_names)
      }
    } else if (op == "~~") {
      base_name <- paste0(lhs, "..", rhs)

      if (group_num == 1) {
        return(base_name)
      } else {
        return(paste0(base_name, ".g", group_num))
      }
    } else if (op == "~1" && lhs %in% c("visual", "textual", "speed")) {
      if (group_num > 1) {
        return(paste0(lhs, ".1.g", group_num))
      }
    }

    return(NULL)
  }

  add_significance_stars <- function(est_values, lower_values, upper_values) {
    significant <- !is.na(lower_values) & !is.na(upper_values) &
      (lower_values > 0 | upper_values < 0)
    ifelse(significant, paste0(est_values, "*"), est_values)
  }

  add_significance_indicator <- function(lower_values, upper_values) {
    significant <- !is.na(lower_values) & !is.na(upper_values) &
      (lower_values > 0 | upper_values < 0)
    ifelse(significant, 0, NA_real_)  # 0 for significant, NA for not
  }

  hpd_names <- hpd_intervals$parameter
  is_blavaan_format <- any(grepl("^\\.p[0-9]+\\.$", hpd_names))

  if (is_blavaan_format) {
    edges_with_hpd <- tidysem_obj$edges |>
      rowwise() |>
      mutate(
        hpd_name = {
          if (!is.na(lavaan_label) && lavaan_label != "" && grepl("^\\.p[0-9]+\\.$", lavaan_label)) {
            label <- lavaan_label
            group_num <- match(group, group_info)
            if (is.na(group_num)) group_num <- 1

            if (group_num == 1) {
              # Group 1
              label
            } else {
              # Group 2 - try different patterns
              name1 <- paste0(label, "..", group_num)
              name2 <- paste0(label, ".g", group_num)

              if (name1 %in% hpd_names) name1
              else if (name2 %in% hpd_names) name2
              else label  # fallback
            }
          } else if (op == "~~") {
            # Variance or covariance
            base_name <- paste0(lhs, "..", rhs)
            group_num <- match(group, group_info)
            if (is.na(group_num)) group_num == 1

            if (group_num == 1) {
              base_name
            } else {
              paste0(base_name, ".g", group_num)
            }
          } else {
            NA_character_
          }
        }
      ) |>
      ungroup() |>
      left_join(hpd_intervals |> select(parameter, lower, upper),
                by = c("hpd_name" = "parameter")) |>
      select(-hpd_name)

  } else {
    edges_with_hpd <- tidysem_obj$edges |>
      mutate(
        param_name = case_when(
          op == "=~" ~ paste0(lhs, op, rhs),
          op == "~~" ~ paste0(lhs, op, rhs),
          op == "~1" ~ paste0(lhs, op),
          TRUE ~ paste0(lhs, op, rhs)
        )
      ) |>
      left_join(hpd_intervals |> select(parameter, lower, upper),
                by = c("param_name" = "parameter")) |>
      select(-param_name)
  }

  if (!is.null(std_hdi_intervals)) {
    edges_with_hpd <- edges_with_hpd |>
      mutate(group = as.character(group))

    std_hdi_intervals <- std_hdi_intervals |>
      mutate(group_name = as.character(group_name))

    edges_with_hpd <- edges_with_hpd |>
      left_join(std_hdi_intervals |>
                  select(lhs, op, rhs, group_name, CI_low_std = CI_low, CI_high_std = CI_high, conf_int_std),
                by = c("lhs", "op", "rhs", "group" = "group_name"))
  }

  tidysem_obj$edges <- edges_with_hpd |>
    mutate(
      est_num = as.numeric(est),
      est_std_num = as.numeric(est_std),

      confint = case_when(
        !is.na(lower) & !is.na(upper) ~
          paste0("[", formatC(lower, format = "f", digits = 2), ", ", formatC(upper, format = "f", digits = 2), "]"),
        TRUE ~
          paste0("[", formatC(est_num, format = "f", digits = 2), ", ", formatC(est_num, format = "f", digits = 2), "]")
      ),

      confint_std = if (!is.null(std_hdi_intervals) && "conf_int_std" %in% names(edges_with_hpd)) {
        case_when(
          !is.na(conf_int_std) ~ conf_int_std,
          TRUE ~ paste0("[", formatC(est_std_num, format = "f", digits = 2), ", ", formatC(est_std_num, format = "f", digits = 2), "]")
        )
      } else if ("CI_low_std" %in% names(edges_with_hpd) && "CI_high_std" %in% names(edges_with_hpd)) {
        case_when(
          !is.na(CI_low_std) & !is.na(CI_high_std) ~
            paste0("[", formatC(CI_low_std, format = "f", digits = 2), ", ", formatC(CI_high_std, format = "f", digits = 2), "]"),
          TRUE ~ paste0("[", formatC(est_std_num, format = "f", digits = 2), ", ", formatC(est_std_num,  format = "f", digits = 2), "]")
        )
      } else {
        paste0("[", formatC(est_std_num, format = "f", digits = 2), ", ", formatC(est_std_num, format = "f", digits = 2), "]")
      },

      pval = add_significance_indicator(lower, upper),

      est_sig = if (p_val) add_significance_stars(est, lower, upper) else est,
      est_sig_std = if (p_val) {
        if (!is.null(std_hdi_intervals) && "CI_low_std" %in% names(edges_with_hpd) && "CI_high_std" %in% names(edges_with_hpd)) {
          add_significance_stars(est_std, CI_low_std, CI_high_std)
        } else {
          add_significance_stars(est_std, lower, upper)
        }
      } else {
        est_std
      }
    ) |>
    select(-est_num, -est_std_num, -lower, -upper, -any_of(c("CI_low_std", "CI_high_std", "conf_int_std")))

  tidysem_obj$edges <- tidysem_obj$edges |>
    mutate(
      label = case_when(
        standardized == FALSE & unstandardized == FALSE ~
          if (conf_int) {
            ifelse(!is.na(confint), confint, "")
          } else {
            ""
          },

        standardized == TRUE & unstandardized == TRUE ~
          if (p_val & conf_int) {
            ifelse(!is.na(confint) & !is.na(confint_std),
                   paste0(est_sig, " (", est_sig_std, ")\n", confint),
                   ifelse(!is.na(confint),
                          paste0(est_sig, " (", est_sig_std, ")\n", confint),
                          paste0(est_sig, " (", est_sig_std, ")")))
          } else if (p_val & !conf_int) {
            paste0(est_sig, " (", est_sig_std, ")")
          } else if (!p_val & conf_int) {
            ifelse(!is.na(confint) & !is.na(confint_std),
                   paste0(est, " (", est_std, ")\n", confint),
                   ifelse(!is.na(confint),
                          paste0(est, " (", est_std, ")\n", confint),
                          paste0(est, " (", est_std, ")")))
          } else {
            paste0(est, " (", est_std, ")")
          },

        standardized == TRUE & unstandardized == FALSE ~
          if (p_val & conf_int) {
            ifelse(!is.na(confint_std),
                   paste0(est_sig_std, "\n", confint_std),
                   est_sig_std)
          } else if (p_val & !conf_int) {
            est_sig_std
          } else if (!p_val & conf_int) {
            ifelse(!is.na(confint_std),
                   paste0(est_std, "\n", confint_std),
                   est_std)
          } else {
            as.character(est_std)
          },

        standardized == FALSE & unstandardized == TRUE ~
          if (p_val & conf_int) {
            ifelse(!is.na(confint),
                   paste0(est_sig, "\n", confint),
                   est_sig)
          } else if (p_val & !conf_int) {
            est_sig
          } else if (!p_val & conf_int) {
            ifelse(!is.na(confint),
                   paste0(est, "\n", confint),
                   est)
          } else {
            as.character(est)
          },

        # Fallback
        TRUE ~ ""
      )
    )

  return(tidysem_obj)
}


combine_tidysem_groups <- function(tidysem_obj, group1 = "", group2 = "",
                                   sep_by = " | ", standardized = FALSE, unstandardized = TRUE,
                                   p_val = TRUE, conf_int = FALSE) {

  tidysem_obj$edges <- tidysem_obj$edges |>
    mutate(across(c(est, est_std), ~ as.numeric(as.character(.x))))

  tidysem_obj$nodes <- tidysem_obj$nodes |>
    filter(group == group1) |>
    mutate(across(c(est, est_std), ~ as.numeric(as.character(.x))))

  original_edges_order <- tidysem_obj$edges |>
    distinct(lhs, op, rhs) |>
    mutate(original_order = row_number())

  edges_combined <- tidysem_obj$edges |>
    group_by(lhs, op, rhs) |>
    summarise(
      est_combined = if (all(c(group1, group2) %in% group)) {
        paste(
          ifelse(is.na(est[group == group1]), "NA", round(est[group == group1], 2)),
          ifelse(is.na(est[group == group2]), "NA", round(est[group == group2], 2)),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      est_sig_combined = if (all(c(group1, group2) %in% group)) {
        paste(
          ifelse(is.na(est_sig[group == group1]), "NA", est_sig[group == group1]),
          ifelse(is.na(est_sig[group == group2]), "NA", est_sig[group == group2]),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      est_std_combined = if (all(c(group1, group2) %in% group)) {
        paste(
          ifelse(is.na(est_std[group == group1]), "NA", round(est_std[group == group1], 2)),
          ifelse(is.na(est_std[group == group2]), "NA", round(est_std[group == group2], 2)),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      est_sig_std_combined = if (all(c(group1, group2) %in% group)) {
        paste(
          ifelse(is.na(est_sig_std[group == group1]), "NA", est_sig_std[group == group1]),
          ifelse(is.na(est_sig_std[group == group2]), "NA", est_sig_std[group == group2]),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      confint_combined = if (all(c(group1, group2) %in% group)) {
        paste(
          ifelse(is.na(confint[group == group1]), "NA", confint[group == group1]),
          ifelse(is.na(confint[group == group2]), "NA", confint[group == group2]),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      pval = if ("pval" %in% names(tidysem_obj$edges)) {
        pval1 <- pval[group == group1]
        pval2 <- pval[group == group2]

        if (!is.na(pval1) && !is.na(pval2) && pval1 < 0.001 && pval2 < 0.001) {
          "0.00"
        } else {
          paste(pval1, pval2, sep = sep_by)
        }
      } else {
        NA_character_
      },
      confint_std_combined = if (all(c(group1, group2) %in% group)) {
        paste(
          ifelse(is.na(confint_std[group == group1]), "NA", confint_std[group == group1]),
          ifelse(is.na(confint_std[group == group2]), "NA", confint_std[group == group2]),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      across(c(from, to, arrow, connect_from, connect_to, curvature,
               linetype, block, show, label_results), first),
      .groups = 'drop'
    ) |>
    filter(!is.na(est_combined)) |>
    mutate(
      across(c(est_combined, est_sig_combined, est_std_combined, est_sig_std_combined,
               confint_combined, confint_std_combined),
             ~ ifelse(.x == paste0("NA", sep_by, "NA"), "", .x))
    ) |>
    mutate(
      label = case_when(
        # Case 1: Both standardized and unstandardized are FALSE
        standardized == FALSE & unstandardized == FALSE ~
          if (conf_int) {
            confint_combined
          } else {
            ""
          },

        # Case 2: Both standardized and unstandardized
        standardized == TRUE & unstandardized == TRUE ~
          if (p_val) {
            if (conf_int) {
              paste0(est_sig_combined, " (", est_sig_std_combined, ")\n", confint_combined)
            } else {
              paste0(est_sig_combined, " (", est_sig_std_combined, ")")
            }
          } else {
            if (conf_int) {
              paste0(est_combined, " (", est_std_combined, ")\n", confint_combined)
            } else {
              paste0(est_combined, " (", est_std_combined, ")")
            }
          },

        # Case 3: Only standardized
        standardized == TRUE & unstandardized == FALSE ~
          if (p_val) {
            if (conf_int) {
              paste0(est_sig_std_combined, "\n", confint_std_combined)
            } else {
              est_sig_std_combined
            }
          } else {
            if (conf_int) {
              paste0(est_std_combined, "\n", confint_std_combined)
            } else {
              est_std_combined
            }
          },

        # Case 4: Only unstandardized
        standardized == FALSE & unstandardized == TRUE ~
          if (p_val) {
            if (conf_int) {
              paste0(est_sig_combined, "\n", confint_combined)
            } else {
              est_sig_combined
            }
          } else {
            if (conf_int) {
              paste0(est_combined, "\n", confint_combined)
            } else {
              est_combined
            }
          },

        TRUE ~ ""  # Fallback (shouldn't be reached)
      )
    ) |>
    left_join(original_edges_order, by = c("lhs", "op", "rhs")) |>
    arrange(original_order) |>
    select(-original_order)

  tidysem_obj$edges <- edges_combined
  tidysem_obj$edges$group <- NULL
  tidysem_obj$nodes$group <- NULL

  return(tidysem_obj)
}

combine_tidysem_objects <- function(tidysem_obj1, tidysem_obj2, group1 = "Group1", group2 = "Group2",
                                    sep_by = " | ", standardized = FALSE, unstandardized = TRUE,
                                    p_val = TRUE, conf_int = FALSE) {

  if (!all(c("edges", "nodes") %in% names(tidysem_obj1)) ||
      !all(c("edges", "nodes") %in% names(tidysem_obj2))) {
    stop("Both objects must be tidySEM graph objects with 'edges' and 'nodes' components")
  }

  tidysem_obj1$edges$group <- group1
  tidysem_obj1$nodes$group <- group1

  tidysem_obj2$edges$group <- group2
  tidysem_obj2$nodes$group <- group2

  original_edges_order <- tidysem_obj1$edges |>
    distinct(lhs, op, rhs) |>
    mutate(original_order = row_number())

  edges_combined <- bind_rows(tidysem_obj1$edges, tidysem_obj2$edges)

  nodes_combined <- tidysem_obj1$nodes

  edges_combined <- edges_combined |>
    mutate(across(any_of(c("est", "est_std")), ~ as.numeric(as.character(.x))))

  nodes_combined <- nodes_combined |>
    mutate(across(any_of(c("est", "est_std")), ~ as.numeric(as.character(.x))))

  edges_final <- edges_combined |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>  # Only keep parameters that exist in both groups
    summarise(
      est_combined = paste(
        round(est[group == group1], 2),
        round(est[group == group2], 2),
        sep = sep_by
      ),
      est_sig_combined = if ("est_sig" %in% names(edges_combined)) {
        paste(
          est_sig[group == group1],
          est_sig[group == group2],
          sep = sep_by
        )
      } else {
        paste(
          round(est[group == group1], 2),
          round(est[group == group2], 2),
          sep = sep_by
        )
      },
      est_std_combined = if ("est_std" %in% names(edges_combined)) {
        paste(
          round(est_std[group == group1], 2),
          round(est_std[group == group2], 2),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      est_sig_std_combined = if (all(c("est_sig_std") %in% names(edges_combined))) {
        paste(
          est_sig_std[group == group1],
          est_sig_std[group == group2],
          sep = sep_by
        )
      } else if ("est_std" %in% names(edges_combined)) {
        paste(
          round(est_std[group == group1], 2),
          round(est_std[group == group2], 2),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      confint_combined = if ("confint" %in% names(edges_combined)) {
        paste(
          confint[group == group1],
          confint[group == group2],
          sep = sep_by
        )
      } else {
        NA_character_
      },
      pval = if ("pval" %in% names(edges_combined)) {
        pval1 <- pval[group == group1]
        pval2 <- pval[group == group2]

        if (!is.na(pval1) && !is.na(pval2) && pval1 < 0.001 && pval2 < 0.001) {
          "0.00"
        } else {
          paste(pval1, pval2, sep = sep_by)
        }
      } else {
        NA_character_
      },
      confint_std_combined = if ("confint_std" %in% names(edges_combined)) {
        paste(
          confint_std[group == group1],
          confint_std[group == group2],
          sep = sep_by
        )
      } else {
        NA_character_
      },
      across(any_of(c("from", "to", "arrow", "connect_from", "connect_to",
                      "curvature", "linetype", "block", "show", "label_results")),
             ~ first(na.omit(.x))),
      .groups = 'drop'
    ) |>
    mutate(
      label = case_when(
        # Case 1: Both standardized and unstandardized are FALSE
        standardized == FALSE & unstandardized == FALSE ~
          if (conf_int) {
            # Show unstandardized confidence interval without "\n"
            confint_combined
          } else {
            ""
          },

        # Case 2: Both standardized and unstandardized
        standardized == TRUE & unstandardized == TRUE ~
          if (p_val) {
            if (conf_int) {
              paste0(est_sig_combined, " (", est_sig_std_combined, ")\n", confint_combined)
            } else {
              paste0(est_sig_combined, " (", est_sig_std_combined, ")")
            }
          } else {
            if (conf_int) {
              paste0(est_combined, " (", est_std_combined, ")\n", confint_combined)
            } else {
              paste0(est_combined, " (", est_std_combined, ")")
            }
          },

        # Case 3: Only standardized
        standardized == TRUE & unstandardized == FALSE ~
          if (p_val) {
            if (conf_int) {
              paste0(est_sig_std_combined, "\n", confint_std_combined)
            } else {
              est_sig_std_combined
            }
          } else {
            if (conf_int) {
              paste0(est_std_combined, "\n", confint_std_combined)
            } else {
              est_std_combined
            }
          },

        # Case 4: Only unstandardized
        standardized == FALSE & unstandardized == TRUE ~
          if (p_val) {
            if (conf_int) {
              paste0(est_sig_combined, "\n", confint_combined)
            } else {
              est_sig_combined
            }
          } else {
            if (conf_int) {
              paste0(est_combined, "\n", confint_combined)
            } else {
              est_combined
            }
          },

        # Fallback (shouldn't be reached)
        TRUE ~ ""
      )
    ) |>
    left_join(original_edges_order, by = c("lhs", "op", "rhs")) |>
    arrange(original_order) |>
    select(-original_order)

  nodes_combined <- nodes_combined |> select(-group)



  combined_tidysem <- list(
    edges = edges_final,
    nodes = nodes_combined
  )

  if (!is.null(attr(tidysem_obj1, "class"))) {
    class(combined_tidysem) <- class(tidysem_obj1)
  }

  combined_tidysem$edges$group <- NULL
  combined_tidysem$nodes$group <- NULL

  return(combined_tidysem)
}



combine_tidysem_objects_bayes <- function(tidysem_obj1, tidysem_obj2, blavaan_fit1, blavaan_fit2,
                                          group1 = "Group1", group2 = "Group2",
                                          sep_by = " | ", standardized = FALSE, unstandardized = TRUE,
                                          p_val = TRUE, conf_int = FALSE, ci_level = 0.95) {

  if (!all(c("edges", "nodes") %in% names(tidysem_obj1)) ||
      !all(c("edges", "nodes") %in% names(tidysem_obj2))) {
    stop("Both objects must be tidySEM graph objects with 'edges' and 'nodes' components")
  }

  hpd_intervals1 <- as.data.frame(blavaan::blavInspect(blavaan_fit1, "hpd", level = ci_level))
  hpd_intervals1$parameter <- rownames(hpd_intervals1)

  hpd_intervals2 <- as.data.frame(blavaan::blavInspect(blavaan_fit2, "hpd", level = ci_level))
  hpd_intervals2$parameter <- rownames(hpd_intervals2)

  if ((standardized == TRUE && conf_int == TRUE && unstandardized == FALSE)) {
    std_hdi1 <- get_standardized_hdi(blavaan_fit1, ci_level = ci_level)
    std_hdi2 <- get_standardized_hdi(blavaan_fit2, ci_level = ci_level)
  }

  add_significance_stars <- function(est_values, lower_values, upper_values) {
    significant <- !is.na(lower_values) & !is.na(upper_values) &
      (lower_values > 0 | upper_values < 0)
    ifelse(significant, paste0(est_values, "*"), est_values)
  }

  original_edges_order <- tidysem_obj1$edges |>
    distinct(lhs, op, rhs) |>
    mutate(original_order = row_number())

  tidysem_obj1$edges <- tidysem_obj1$edges |>
    mutate(
      param_name = case_when(
        op == "=~" ~ paste0(lhs, op, rhs),
        op == "~~" & lhs != rhs ~ paste0(lhs, op, rhs),
        op == "~~" & lhs == rhs ~ paste0("Variances.", lhs),
        op == "~1" ~ paste0("Means.", lhs),
        TRUE ~ paste0(lhs, op, rhs)
      ),
      est_num = as.numeric(as.character(est))
    ) |>
    left_join(hpd_intervals1 |> select(parameter, lower, upper),
              by = c("param_name" = "parameter"))

  if (exists("std_hdi1")) {
    tidysem_obj1$edges <- tidysem_obj1$edges |>
      left_join(std_hdi1 |>
                  select(lhs, op, rhs, CI_low_std = CI_low, CI_high_std = CI_high),
                by = c("lhs", "op", "rhs"))
  }

  tidysem_obj1$edges <- tidysem_obj1$edges |>
    mutate(
      confint = ifelse(!is.na(lower) & !is.na(upper),
                       paste0("[", formatC(lower, format = "f", digits = 2), ", ", formatC(upper, format = "f", digits = 2), "]"),
                       paste0("[", formatC(est_num, format = "f", digits = 2), ", ", formatC(est_num, format = "f", digits = 2), "]")),
      confint_std = if (exists("CI_low_std") && exists("CI_high_std")) {
        ifelse(!is.na(CI_low_std) & !is.na(CI_high_std),
               paste0("[", formatC(CI_low_std, format = "f", digits = 2), ", ", formatC(CI_high_std, format = "f", digits = 2), "]"),
               ifelse(!is.na(est_std),
                      paste0("[", formatC(as.numeric(as.character(est_std)), format = "f", digits = 2), ", ",
                             formatC(as.numeric(as.character(est_std)), format = "f", digits = 2), "]"),
                      NA_character_))
      } else {
        ifelse(!is.na(est_std),
               paste0("[", formatC(as.numeric(as.character(est_std)), format = "f", digits = 2), ", ",
                      formatC(as.numeric(as.character(est_std)), format = "f", digits = 2), "]"),
               NA_character_)
      },
      est_sig = if (p_val) add_significance_stars(est, lower, upper) else as.character(est),
      est_sig_std = if (p_val) {
        if (exists("CI_low_std") && exists("CI_high_std")) {
          add_significance_stars(est_std, CI_low_std, CI_high_std)
        } else {
          add_significance_stars(est_std, lower, upper)
        }
      } else {
        as.character(est_std)
      },
      group = group1
    ) |>
    select(-param_name, -lower, -upper, -est_num, -any_of(c("CI_low_std", "CI_high_std")))

  tidysem_obj2$edges <- tidysem_obj2$edges |>
    mutate(
      param_name = case_when(
        op == "=~" ~ paste0(lhs, op, rhs),
        op == "~~" & lhs != rhs ~ paste0(lhs, op, rhs),
        op == "~~" & lhs == rhs ~ paste0("Variances.", lhs),
        op == "~1" ~ paste0("Means.", lhs),
        TRUE ~ paste0(lhs, op, rhs)
      ),
      est_num = as.numeric(as.character(est))
    ) |>
    left_join(hpd_intervals2 |> select(parameter, lower, upper),
              by = c("param_name" = "parameter"))

  if (exists("std_hdi2")) {
    tidysem_obj2$edges <- tidysem_obj2$edges |>
      left_join(std_hdi2 |>
                  select(lhs, op, rhs, CI_low_std = CI_low, CI_high_std = CI_high),
                by = c("lhs", "op", "rhs"))
  }

  tidysem_obj2$edges <- tidysem_obj2$edges |>
    mutate(
      confint = ifelse(!is.na(lower) & !is.na(upper),
                       paste0("[", round(lower, 2), ", ", round(upper, 2), "]"),
                       paste0("[", round(est_num, 2), ", ", round(est_num, 2), "]")),
      confint_std = if (exists("CI_low_std") && exists("CI_high_std")) {
        ifelse(!is.na(CI_low_std) & !is.na(CI_high_std),
               paste0("[", round(CI_low_std, 2), ", ", round(CI_high_std, 2), "]"),
               ifelse(!is.na(est_std),
                      paste0("[", round(as.numeric(as.character(est_std)), 2), ", ",
                             round(as.numeric(as.character(est_std)), 2), "]"),
                      NA_character_))
      } else {
        ifelse(!is.na(est_std),
               paste0("[", round(as.numeric(as.character(est_std)), 2), ", ",
                      round(as.numeric(as.character(est_std)), 2), "]"),
               NA_character_)
      },
      est_sig = if (p_val) add_significance_stars(est, lower, upper) else as.character(est),
      est_sig_std = if (p_val) {
        if (exists("CI_low_std") && exists("CI_high_std")) {
          add_significance_stars(est_std, CI_low_std, CI_high_std)
        } else {
          add_significance_stars(est_std, lower, upper)
        }
      } else {
        as.character(est_std)
      },
      group = group2
    ) |>
    select(-param_name, -lower, -upper, -est_num, -any_of(c("CI_low_std", "CI_high_std")))

  edges_combined <- bind_rows(tidysem_obj1$edges, tidysem_obj2$edges)

  edges_combined <- edges_combined |>
    mutate(across(any_of(c("est", "est_std")), ~ as.numeric(as.character(.x))))

  edges_final <- edges_combined |>
    group_by(lhs, op, rhs) |>
    filter(dplyr::n() == 2) |>
    summarise(
      group1_est_num = est[group == group1],
      group2_est_num = est[group == group2],
      group1_est_std_num = if ("est_std" %in% names(edges_combined)) est_std[group == group1] else NA_real_,
      group2_est_std_num = if ("est_std" %in% names(edges_combined)) est_std[group == group2] else NA_real_,

      est_combined = paste(
        ifelse(is.na(est[group == group1]), "NA", round(est[group == group1], 2)),
        ifelse(is.na(est[group == group2]), "NA", round(est[group == group2], 2)),
        sep = sep_by
      ),
      est_sig_combined = paste(
        ifelse(is.na(est_sig[group == group1]), "NA", est_sig[group == group1]),
        ifelse(is.na(est_sig[group == group2]), "NA", est_sig[group == group2]),
        sep = sep_by
      ),
      est_std_combined = if ("est_std" %in% names(edges_combined)) {
        paste(
          ifelse(is.na(est_std[group == group1]), "NA", round(est_std[group == group1], 2)),
          ifelse(is.na(est_std[group == group2]), "NA", round(est_std[group == group2], 2)),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      est_sig_std_combined = if ("est_sig_std" %in% names(edges_combined)) {
        paste(
          ifelse(is.na(est_sig_std[group == group1]), "NA", est_sig_std[group == group1]),
          ifelse(is.na(est_sig_std[group == group2]), "NA", est_sig_std[group == group2]),
          sep = sep_by
        )
      } else if ("est_std" %in% names(edges_combined)) {
        paste(
          ifelse(is.na(est_std[group == group1]), "NA", round(est_std[group == group1], 2)),
          ifelse(is.na(est_std[group == group2]), "NA", round(est_std[group == group2], 2)),
          sep = sep_by
        )
      } else {
        NA_character_
      },
      confint_combined = paste(
        confint[group == group1],
        confint[group == group2],
        sep = sep_by
      ),
      confint_std_combined = paste(
        confint_std[group == group1],
        confint_std[group == group2],
        sep = sep_by
      ),
      across(any_of(c("from", "to", "arrow", "connect_from", "connect_to",
                      "curvature", "linetype", "block", "show", "label_results")),
             ~ first(na.omit(.x))),
      .groups = 'drop'
    ) |>
    select(-group1_est_num, -group2_est_num, -group1_est_std_num, -group2_est_std_num) |>
    mutate(
      across(c(est_combined, est_sig_combined, est_std_combined, est_sig_std_combined,
               confint_combined, confint_std_combined),
             ~ ifelse(.x == paste0("NA", sep_by, "NA"), "", .x))
    ) |>
    mutate(
      label = case_when(
        standardized == FALSE & unstandardized == FALSE ~
          if (conf_int) {
            ifelse(confint_combined != "", confint_combined, "")
          } else {
            ""  # Returns "" for all rows in this case
          },

        standardized == TRUE & unstandardized == TRUE ~
          if (p_val) {
            if (conf_int) {
              # p_val = TRUE, conf_int = TRUE
              ifelse(confint_combined != "",
                     paste0(est_sig_combined, " (", est_sig_std_combined, ")\n", confint_combined),
                     paste0(est_sig_combined, " (", est_sig_std_combined, ")"))
            } else {
              # p_val = TRUE, conf_int = FALSE
              paste0(est_sig_combined, " (", est_sig_std_combined, ")")
            }
          } else {
            if (conf_int) {
              # p_val = FALSE, conf_int = TRUE
              ifelse(confint_combined != "",
                     paste0(est_combined, " (", est_std_combined, ")\n", confint_combined),
                     paste0(est_combined, " (", est_std_combined, ")"))
            } else {
              # p_val = FALSE, conf_int = FALSE
              paste0(est_combined, " (", est_std_combined, ")")
            }
          },

        # Case 3: Only standardized
        standardized == TRUE & unstandardized == FALSE ~
          if (p_val) {
            if (conf_int) {
              # p_val = TRUE, conf_int = TRUE
              ifelse(confint_std_combined != "",
                     paste0(est_sig_std_combined, "\n", confint_std_combined),
                     est_sig_std_combined)
            } else {
              # p_val = TRUE, conf_int = FALSE
              est_sig_std_combined
            }
          } else {
            if (conf_int) {
              # p_val = FALSE, conf_int = TRUE
              ifelse(confint_std_combined != "",
                     paste0(est_std_combined, "\n", confint_std_combined),
                     est_std_combined)
            } else {
              # p_val = FALSE, conf_int = FALSE
              est_std_combined
            }
          },

        # Case 4: Only unstandardized
        standardized == FALSE & unstandardized == TRUE ~
          if (p_val) {
            if (conf_int) {
              # p_val = TRUE, conf_int = TRUE
              ifelse(confint_combined != "",
                     paste0(est_sig_combined, "\n", confint_combined),
                     est_sig_combined)
            } else {
              # p_val = TRUE, conf_int = FALSE
              est_sig_combined
            }
          } else {
            if (conf_int) {
              # p_val = FALSE, conf_int = TRUE
              ifelse(confint_combined != "",
                     paste0(est_combined, "\n", confint_combined),
                     est_combined)
            } else {
              # p_val = FALSE, conf_int = FALSE
              est_combined
            }
          },

        # Fallback (shouldn't be reached)
        TRUE ~ ""
      )
    ) |>
    left_join(original_edges_order, by = c("lhs", "op", "rhs")) |>
    arrange(original_order) |>
    select(-original_order)

  nodes_final <- tidysem_obj1$nodes

  combined_tidysem <- list(
    edges = edges_final,
    nodes = nodes_final
  )

  if (!is.null(attr(tidysem_obj1, "class"))) {
    class(combined_tidysem) <- class(tidysem_obj1)
  }

  combined_tidysem$edges$group <- NULL
  combined_tidysem$nodes$group <- NULL

  return(combined_tidysem)
}

combine_tidysem_groups_bayes <- function(tidysem_obj, blavaan_fit, group1 = "", group2 = "",
                                         sep_by = " | ", standardized = FALSE, unstandardized = TRUE,
                                         p_val = FALSE, conf_int = FALSE, ci_level = 0.95) {

  if ((standardized == TRUE && unstandardized == FALSE && conf_int == TRUE)) {
    get_std_ci <- TRUE
  } else {
    get_std_ci <- FALSE
  }

  hpd_intervals <- as.data.frame(blavaan::blavInspect(blavaan_fit, "hpd", level = ci_level))
  hpd_intervals$parameter <- rownames(hpd_intervals)

  group_info <- blavaan::blavInspect(blavaan_fit, "group.label")

  if (group1 == "" && length(group_info) >= 1) {
    group1 <- group_info[1]
  }
  if (group2 == "" && length(group_info) >= 2) {
    group2 <- group_info[2]
  }

  group1_num <- which(group_info == group1)
  group2_num <- which(group_info == group2)
  if (length(group1_num) == 0) group1_num <- 1
  if (length(group2_num) == 0) group2_num <- 2

  std_hdi_intervals <- NULL
  if (get_std_ci) {
    std_hdi_intervals <- get_standardized_hdi(blavaan_fit, ci_level = ci_level)
  }

  add_significance_stars <- function(est_values, lower_values, upper_values) {
    significant <- !is.na(lower_values) & !is.na(upper_values) &
      (lower_values > 0 | upper_values < 0)
    ifelse(significant, paste0(est_values, "*"), est_values)
  }

  add_significance_indicator <- function(lower_values, upper_values) {
    significant <- !is.na(lower_values) & !is.na(upper_values) &
      (lower_values > 0 | upper_values < 0)
    ifelse(significant, 0, NA_real_)  # 0 for significant, NA for not
  }

  original_edges_order <- tidysem_obj$edges |>
    distinct(lhs, op, rhs) |>
    mutate(original_order = row_number())

  hpd_names <- hpd_intervals$parameter
  is_blavaan_format <- any(grepl("^\\.p[0-9]+\\.$", hpd_names))

  if (is_blavaan_format) {
    edges_to_process <- tidysem_obj$edges |>
      filter(group %in% c(group1, group2)) |>
      mutate(
        est_num = as.numeric(as.character(est))
      )

    edges_to_process <- edges_to_process |>
      rowwise() |>
      mutate(
        param_base = {
          if (op == "=~") {
            if (!is.na(lavaan_label) && lavaan_label != "" && grepl("^\\.p[0-9]+\\.$", lavaan_label)) {
              lavaan_label
            } else {
              paste0(lhs, "=~", rhs)
            }
          } else if (op == "~~") {
            paste0(lhs, "..", rhs)
          } else if (op == "~1") {
            if (!is.na(lavaan_label) && lavaan_label != "" && grepl("^\\.p[0-9]+\\.$", lavaan_label)) {
              lavaan_label
            } else if (lhs %in% c("visual", "textual", "speed") && group == group2) {
              paste0(lhs, ".1.g", group2_num)
            } else {
              paste0("Means.", lhs)  # Fallback
            }
          } else {
            paste0(lhs, op, rhs)
          }
        }
      ) |>
      mutate(
        hpd_param_name = {
          current_group_num <- ifelse(group == group1, group1_num, group2_num)

          result <- NA_character_

          if (current_group_num == 1 && param_base %in% hpd_names) {
            result <- param_base
          } else {
            patterns <- c(
              paste0(param_base, ".g", current_group_num),
              paste0(param_base, "..", current_group_num),
              param_base
            )

            for (pattern in patterns) {
              if (pattern %in% hpd_names) {
                result <- pattern
                break
              }
            }
          }

          result
        }
      ) |>
      ungroup() |>
      left_join(hpd_intervals |> select(parameter, lower, upper),
                by = c("hpd_param_name" = "parameter")) |>
      select(-hpd_param_name, -param_base)

  } else {
    edges_to_process <- tidysem_obj$edges |>
      filter(group %in% c(group1, group2)) |>
      mutate(
        est_num = as.numeric(as.character(est)),
        param_name = case_when(
          op == "=~" ~ paste0(lhs, op, rhs),
          op == "~~" ~ paste0(lhs, op, rhs),
          op == "~1" ~ paste0(lhs, op),
          TRUE ~ paste0(lhs, op, rhs)
        )
      ) |>
      left_join(hpd_intervals |> select(parameter, lower, upper),
                by = c("param_name" = "parameter")) |>
      select(-param_name)
  }

  if ("est_std" %in% names(edges_to_process)) {
    edges_to_process <- edges_to_process |>
      mutate(est_std_num = as.numeric(as.character(est_std)))
  } else {
    edges_to_process <- edges_to_process |>
      mutate(est_std_num = NA_real_)
  }

  if (!is.null(std_hdi_intervals)) {
    edges_to_process <- edges_to_process |>
      mutate(group = as.character(group))

    std_hdi_intervals <- std_hdi_intervals |>
      mutate(group_name = as.character(group_name))

    edges_to_process <- edges_to_process |>
      left_join(std_hdi_intervals |>
                  select(lhs, op, rhs, group_name, CI_low_std = CI_low, CI_high_std = CI_high, conf_int_std),
                by = c("lhs", "op", "rhs", "group" = "group_name"))
  }

  edges_to_process <- edges_to_process |>
    mutate(
      confint = case_when(
        !is.na(lower) & !is.na(upper) ~
          paste0("[", formatC(lower, format = "f", digits = 2), ", ", formatC(upper, format = "f", digits = 2), "]"),
        TRUE ~
          paste0("[", formatC(est_num, format = "f", digits = 2), ", ", formatC(est_num, format = "f", digits = 2), "]")
      ),
      confint_std = if (!is.null(std_hdi_intervals) && "conf_int_std" %in% names(edges_to_process)) {
        case_when(
          !is.na(conf_int_std) ~ conf_int_std,
          TRUE ~ paste0("[", formatC(est_std_num, format = "f", digits = 2), ", ", formatC(est_std_num, format = "f", digits = 2), "]")
        )
      } else if ("CI_low_std" %in% names(edges_to_process) && "CI_high_std" %in% names(edges_to_process)) {
        case_when(
          !is.na(CI_low_std) & !is.na(CI_high_std) ~
            paste0("[", formatC(CI_low_std, format = "f", digits = 2), ", ", formatC(CI_high_std, format = "f", digits = 2), "]"),
          TRUE ~ paste0("[", formatC(est_std_num, format = "f", digits = 2), ", ", formatC(est_std_num, format = "f", digits = 2), "]")
        )
      } else {
        paste0("[", formatC(est_std_num, format = "f", digits = 2), ", ", formatC(est_std_num, format = "f", digits = 2), "]")
      },

      pval = add_significance_indicator(lower, upper),

      est_sig = if (p_val) add_significance_stars(est, lower, upper) else est,
      est_sig_std = if (p_val) {
        if (!is.null(std_hdi_intervals) && "CI_low_std" %in% names(edges_to_process) && "CI_high_std" %in% names(edges_to_process)) {
          add_significance_stars(est_std, CI_low_std, CI_high_std)
        } else {
          add_significance_stars(est_std, lower, upper)
        }
      } else {
        est_std
      }
    ) |>
    select(-est_num, -est_std_num, -lower, -upper, -any_of(c("CI_low_std", "CI_high_std", "conf_int_std")))

  edges_combined <- edges_to_process |>
    mutate(
      across(c(est, est_std), ~ as.numeric(as.character(.x)))
    ) |>
    group_by(lhs, op, rhs) |>
    filter(n() == 2) |>
    summarise(
      est_group1 = as.numeric(est[group == group1]),
      est_group2 = as.numeric(est[group == group2]),
      est_sig_group1 = est_sig[group == group1],
      est_sig_group2 = est_sig[group == group2],
      est_std_group1 = as.numeric(est_std[group == group1]),
      est_std_group2 = as.numeric(est_std[group == group2]),
      est_sig_std_group1 = est_sig_std[group == group1],
      est_sig_std_group2 = est_sig_std[group == group2],
      confint_group1 = confint[group == group1],
      confint_group2 = confint[group == group2],
      confint_std_group1 = confint_std[group == group1],
      confint_std_group2 = confint_std[group == group2],

      across(c(from, to, arrow, connect_from, connect_to, curvature,
               linetype, block, show, label_results), ~ .x[group == group1]),
      .groups = 'drop'
    ) |>
    mutate(
      est_combined = paste(
        ifelse(is.na(est_group1), "NA", round(est_group1, 2)),
        ifelse(is.na(est_group2), "NA", round(est_group2, 2)),
        sep = sep_by
      ),
      est_sig_combined = paste(
        ifelse(is.na(est_sig_group1), "NA", est_sig_group1),
        ifelse(is.na(est_sig_group2), "NA", est_sig_group2),
        sep = sep_by
      ),
      est_std_combined = paste(
        ifelse(is.na(est_std_group1), "NA", round(est_std_group1, 2)),
        ifelse(is.na(est_std_group2), "NA", round(est_std_group2, 2)),
        sep = sep_by
      ),
      est_sig_std_combined = paste(
        ifelse(is.na(est_sig_std_group1), "NA", est_sig_std_group1),
        ifelse(is.na(est_sig_std_group2), "NA", est_sig_std_group2),
        sep = sep_by
      ),
      confint_combined = paste(
        confint_group1,
        confint_group2,
        sep = sep_by
      ),
      confint_std_combined = paste(
        confint_std_group1,
        confint_std_group2,
        sep = sep_by
      )
    ) |>
    select(-est_group1, -est_group2, -est_std_group1, -est_std_group2) |>
    mutate(
      across(c(est_combined, est_sig_combined, est_std_combined, est_sig_std_combined,
               confint_combined, confint_std_combined),
             ~ ifelse(.x == paste0("NA", sep_by, "NA"), "", .x))
    ) |>
    mutate(
      label = case_when(
        standardized == TRUE & unstandardized == TRUE ~
          if (p_val) {
            if (conf_int) {
              ifelse(confint_combined != "",
                     paste0(est_sig_combined, " (", est_sig_std_combined, ")\n", confint_combined),
                     paste0(est_sig_combined, " (", est_sig_std_combined, ")"))
            } else {
              paste0(est_sig_combined, " (", est_sig_std_combined, ")")
            }
          } else {
            if (conf_int) {
              ifelse(confint_combined != "",
                     paste0(est_combined, " (", est_std_combined, ")\n", confint_combined),
                     paste0(est_combined, " (", est_std_combined, ")"))
            } else {
              paste0(est_combined, " (", est_std_combined, ")")
            }
          },

        # Only standardized
        standardized == TRUE & unstandardized == FALSE ~
          if (p_val) {
            if (conf_int) {
              # ALWAYS show the confidence interval part when conf_int = TRUE
              # Even for fixed parameters, show \n [est, est]
              paste0(est_sig_std_combined, "\n", confint_std_combined)
            } else {
              est_sig_std_combined
            }
          } else {
            if (conf_int) {
              paste0(est_std_combined, "\n", confint_std_combined)
            } else {
              est_std_combined
            }
          },

        standardized == FALSE & unstandardized == TRUE ~
          if (p_val) {
            if (conf_int) {
              paste0(est_sig_combined, "\n", confint_combined)
            } else {
              est_sig_combined
            }
          } else {
            if (conf_int) {
              # ALWAYS show the confidence interval part when conf_int = TRUE
              paste0(est_combined, "\n", confint_combined)
            } else {
              est_combined
            }
          },

        standardized == FALSE & unstandardized == FALSE ~
          if (conf_int) {
            ifelse(confint_combined != "", confint_combined, "")
          } else {
            ""
          },

        TRUE ~ ""  # Fallback
      )
    ) |>
    left_join(original_edges_order, by = c("lhs", "op", "rhs")) |>
    arrange(original_order) |>
    select(-original_order)

  tidysem_obj$edges <- edges_combined

  tidysem_obj$edges$group <- NULL
  tidysem_obj$nodes$group <- NULL

  return(tidysem_obj)
}

classify_parameters <- function(model,
                                group_labels = NULL,
                                include_reference = TRUE,
                                target_group = NULL) {

  if (!inherits(model, c("lavaan", "blavaan"))) {
    stop("Model must be a lavaan or blavaan object")
  }

  ptable <- lavaan::parTable(model)

  has_group_column <- "group" %in% names(ptable)

  if (has_group_column && length(unique(ptable$group)) > 1) {
    is_multi_group <- TRUE
    n_groups <- length(unique(ptable$group))
  } else {
    is_multi_group <- FALSE
    n_groups <- 1
  }

  if (0 %in% ptable$group) {
    ptable <- ptable[ptable$group != 0, ]
  }

  if (is_multi_group) {
    tryCatch({
      group_info <- lavaan::lavInspect(model, "group.label")
    }, error = function(e) {
      group_info <- paste("Group", sort(unique(ptable$group)))
    })

    if (!is.null(target_group)) {
      if (is.character(target_group) && target_group %in% group_info) {
        group_number <- which(group_info == target_group)
        group_label <- target_group
      }
      else if (is.numeric(target_group) && target_group <= length(group_info)) {
        group_number <- target_group
        group_label <- group_info[target_group]
      } else {
        group_number <- 1
        group_label <- group_info[1]
        warning("Invalid target_group specified. Using first group.")
      }
    } else {
      group_number <- 1
      group_label <- group_info[1]
    }

    ptable <- ptable[ptable$group == group_number, ]

    if (is.null(group_labels)) {
      group_labels <- setNames(group_info, 1:length(group_info))
    }
    ptable$group_label <- group_label

  } else {
    if (is.null(group_labels)) {
      group_labels <- "Single Group"
    }
    ptable$group_label <- group_labels[1]
    ptable$group <- 1
  }

  results <- ptable %>%
    mutate(
      # Convert to "from" and "to" format
      from = case_when(
        op == "~1" ~ paste0("Intercept_", lhs),
        op == "=~" ~ lhs,
        op == "~~" ~ lhs,
        op == "~"  ~ rhs
      ),
      to = case_when(
        op == "~1" ~ lhs,
        op == "=~" ~ rhs,
        op == "~~" ~ rhs,
        op == "~"  ~ lhs
      ),
      op = op
    ) %>%
    mutate(lhs = from, rhs = to) %>%
    select(-c(from,to)) %>%
    group_by(lhs, op, rhs) |>
    group_modify(~ {
      n_rows <- nrow(.x)

      if (n_rows == 1) {
        if (.x$free[1] == 0) {
          status <- "fixed"
          if (.x$ustart[1] %in% c(0, 1)) {
          } else {
          }
        } else {
          status <- "free"
        }
      } else {
        status <- "multiple_rows_unexpected"
      }

      # Return results
      data.frame(
        param_status = status,
        # constraint_reason = reason,
        group = .x$group,
        group_label = .x$group_label,
        free = .x$free,
        ustart = .x$ustart,
        est = .x$est,
        se = .x$se,
        label = .x$label,
        is_reference = .x$free == 0 & .x$ustart %in% c(0, 1),
        is_estimated = .x$free > 0,
        stringsAsFactors = FALSE
      )
    }) |>
    ungroup()

  if (!include_reference) {
    results <- results |> filter(!is_reference)
  }

  attr(results, "model_info") <- list(
    is_multi_group = is_multi_group,
    n_groups = n_groups,
    selected_group = if(is_multi_group) group_label else NA,
    group_labels = if(is_multi_group) group_info else group_labels
  )

  return(results)
}

classify_parameters_across_groups <- function(model,
                                              group_labels = NULL,
                                              include_reference = TRUE) {

  if (!inherits(model, c("lavaan", "blavaan"))) {
    stop("Model must be a lavaan or blavaan object")
  }

  ptable <- lavaan::parTable(model)
  ptable <- ptable[ptable$group != 0, ]

  if (length(unique(ptable$group)) > 1) {
    n_groups <- length(unique(ptable$group))
    is_multi_group <- TRUE
  } else {
    n_groups <- 1
    is_multi_group <- FALSE
  }

  if (is.null(group_labels)) {
    if (is_multi_group) {
      tryCatch({
        model_groups <- lavaan::lavInspect(model, "group.label")
        if (length(model_groups) == n_groups) {
          group_labels <- model_groups
        } else {
          group_labels <- paste("Group", sort(unique(ptable$group)))
        }
      }, error = function(e) {
        group_labels <- paste("Group", sort(unique(ptable$group)))
      })
    } else {
      group_labels <- "Single Group"
    }
  }

  # Map groups to labels
  unique_groups <- sort(unique(ptable$group))
  if (length(group_labels) == length(unique_groups)) {
    names(group_labels) <- unique_groups
  } else {
    group_labels <- setNames(paste("Group", unique_groups), unique_groups)
  }

  ptable$group_label <- group_labels[as.character(ptable$group)]

  results <- ptable %>%
    mutate(
      # Convert to "from" and "to" format
      from = case_when(
        op == "~1" ~ paste0("Intercept_", lhs),
        op == "=~" ~ lhs,
        op == "~~" ~ lhs,
        op == "~"  ~ rhs
      ),
      to = case_when(
        op == "~1" ~ lhs,
        op == "=~" ~ rhs,
        op == "~~" ~ rhs,
        op == "~"  ~ lhs
      ),
      op = op
    ) %>%
    mutate(lhs = from, rhs = to) %>%
    select(-c(from,to)) %>%
    group_by(lhs, op, rhs) %>%
    group_modify(~ {
      n_rows <- nrow(.x)

      if (n_rows == 1) {
        status <- ifelse(.x$free[1] == 0, "fixed", "free")
        reason <- ifelse(.x$free[1] == 0,
                         ifelse(.x$ustart[1] %in% c(0, 1),
                                "identification_constraint", "user_fixed"),
                         "estimated")
      } else {
        unique_labels <- unique(.x$label[.x$label != ""])

        if (length(unique_labels) == 1) {
          status <- "fixed_equal"
          reason <- "cross_group_constraint"
        } else if (all(.x$free == 0)) {
          status <- "fixed"
          reason <- "all_groups_fixed"
        } else if (all(.x$free > 0)) {
          if (length(unique(.x$est)) == 1) {
            status <- "free_equal"
            reason <- "coincidentally_equal"
          } else {
            status <- "free_varying"
            reason <- "group_specific"
          }
        } else {
          status <- "mixed"
          reason <- "some_fixed_some_free"
        }
      }

      data.frame(
        param_status = status,
        constraint_reason = reason,
        group = .x$group,
        group_label = .x$group_label,
        free = .x$free,
        ustart = .x$ustart,
        est = .x$est,
        se = .x$se,
        label = .x$label,
        is_reference = .x$free == 0 & .x$ustart %in% c(0, 1),
        is_estimated = .x$free > 0,
        stringsAsFactors = FALSE
      )
    }) %>%
    ungroup()

  if (!include_reference) {
    results <- results %>% filter(!is_reference)
  }

  return(results)
}



generate_graph_from_tidySEM <- function(fit, fit_delta, relative_x_position = 25, relative_y_position = 25,
                                        center_x = 0, center_y = 0,
                                        latent_shape = "circle", observed_shape = "square",
                                        int_shape = "triangle",
                                        point_size_latent = 20, point_size_observed = 12,
                                        point_size_int = 10,
                                        width_height_ratio_latent = 1,
                                        width_height_ratio_observed = 1,
                                        width_height_ratio_int = 1,
                                        line_width = 1, line_alpha = 1, text_size_latent = 18, text_font_latent = "sans",
                                        text_color_latent = "#FFFFFF", text_alpha_latent = 1, text_fontface_latent = 'plain',
                                        text_size_others = 16, text_font_others = "sans",
                                        text_color_others = "#FFFFFF", text_alpha_others = 1, text_fontface_others = 'plain',
                                        text_size_edges = 14, text_font_edges = "sans",
                                        text_color_edges =  "#000000", text_color_fill = "#FFFFFF", text_alpha_edges = 1, text_fontface_edges = 'plain',
                                        point_color_latent = "#cc3d3d", point_color_observed = "#1262b3",
                                        point_color_int = "#0f993d",
                                        edge_color = "#000000", line_endpoint_spacing = 1.2,
                                        node_border_color = "#FFFFFF",
                                        node_border_width = 1,
                                        arrow_type = "closed", arrow_size = 0.1,
                                        lavaan_arrow_location = "end",
                                        zoom_factor = 1.2,
                                        lavaan_curvature_magnitude = 0.5,
                                        lavaan_rotate_curvature = FALSE,
                                        lavaan_curvature_asymmetry = 0,
                                        lavaan_curved_x_shift = 0,
                                        lavaan_curved_y_shift = 0,
                                        remove_edgelabels = FALSE,
                                        highlight_free_path = FALSE,
                                        ff_params_edge = NULL,
                                        ff_params_edgelabel = NULL,
                                        ff_params_loop = NULL,
                                        ff_params_looplabel = NULL,
                                        highlight_free_path_multi_group = FALSE,
                                        ff_params_edge_multi = NULL,
                                        ff_params_edgelabel_multi = NULL,
                                        ff_params_loop_multi= NULL,
                                        ff_params_looplabel_multi = NULL,
                                        highlight_sig_path = FALSE,
                                        sig_path_color = "#000000",
                                        non_sig_path_color = "#000000",
                                        sig_label_fontface = "plain",
                                        non_sig_label_fontface = "plain",
                                        highlight_multi_group = FALSE,
                                        sig_diff_edge = NULL,
                                        sig_diff_edgelabel = NULL,
                                        sig_diff_loop = NULL,
                                        sig_diff_looplabel = NULL,
                                        residuals = FALSE,
                                        residuals_orientation_type = 'Graded',
                                        lavaan_loop_offset = 0.8,
                                        lavaan_radius = 2.5,
                                        lavaan_line_color_loop = "#000000",
                                        lavaan_line_alpha_loop = 1,
                                        lavaan_arrow_type_loop = "closed",
                                        lavaan_arrow_size_loop = 0.08,
                                        lavaan_width_loop = 1,
                                        lavaan_height_loop = 1,
                                        lavaan_gap_size_loop = 0.05,
                                        lavaan_two_way_arrow_loop = TRUE,
                                        data_file = NULL,
                                        modify_params_edge = FALSE,
                                        modified_edges = NULL,
                                        modify_params_edgelabel = FALSE,
                                        modified_edgelabels = NULL,
                                        modify_params_edgelabel_xy = FALSE,
                                        modified_edgelabels_xy = NULL,
                                        modify_params_edgelabel_text = FALSE,
                                        modified_edgelabels_text = NULL,
                                        modify_params_node = FALSE,
                                        modified_nodes = NULL,
                                        modify_params_node_xy = FALSE,
                                        modified_nodes_xy = NULL,
                                        modify_params_edge_xy = FALSE,
                                        modified_edges_xy = NULL,
                                        modify_params_cov_edge = FALSE,
                                        modified_cov_edges = NULL,
                                        modify_params_nodelabel = FALSE,
                                        modified_nodelabels = NULL,
                                        modify_params_nodelabel_xy = FALSE,
                                        modified_nodelabels_xy = NULL,
                                        modify_params_nodelabel_text = FALSE,
                                        modified_nodelabels_text = NULL,
                                        modify_params_latent_node_xy = FALSE,
                                        modified_latent_nodes_xy = NULL,
                                        modify_params_latent_node_angle = FALSE,
                                        modified_latent_nodes_angle = NULL,
                                        modify_params_loop = FALSE,
                                        modified_loops = NULL,
                                        modify_params_loop_xy = FALSE,
                                        modified_loops_xy = NULL,
                                        modify_params_loop_location = FALSE,
                                        modified_loops_location = NULL,
                                        modify_params_looplabel = FALSE,
                                        modified_looplabels = NULL,
                                        modify_params_looplabel_xy = FALSE,
                                        modified_looplabels_xy = NULL,
                                        modify_params_looplabel_text = FALSE,
                                        modified_looplabels_text = NULL,
                                        loop_names_remove = NULL,
                                        flip_layout = FALSE,
                                        flip_direction = NULL,
                                        rotate_layout = FALSE,
                                        rotate_angle = 0,
                                        which_group = "1",
                                        group_level = NULL) {

  if (inherits(fit, "sem_graph")) {
    graph_data <- fit

    multi_group <- "group" %in% names(graph_data$nodes) && (length(unique(graph_data$nodes$group)) > 1)

    if (multi_group) {
      nodes_data <- graph_data$nodes[graph_data$nodes$group == group_level,]
      edges_data <- graph_data$edges[graph_data$edges$group == group_level,]
      fit_delta_edges <- fit_delta$edges[fit_delta$edges$group == group_level,]
    } else {
      nodes_data <- graph_data$nodes
      edges_data <- graph_data$edges
      fit_delta_edges <- fit_delta$edges
    }

    node_names <- nodes_data$name
    node_types <- nodes_data$shape

    latent_vars <- node_names[node_types == "oval"]
    observed_vars <- node_names[node_types == "rect"]
    intercept_vars <- node_names[node_types == "triangle"]

    max_val <- max(nodes_data$x, nodes_data$y) * 0.25

    # Normalize x and y relative to the same max
    nodes_data$x <- nodes_data$x / max_val
    nodes_data$y <- nodes_data$y / max_val

    # Extract node coordinates and node names
    node_coords <- as.data.frame(nodes_data[,c('x','y','name')])

    if (flip_layout) {
      flipped <- flip_around_center(node_coords, flip_direction)
      node_coords$x <- flipped$x
      node_coords$y <- flipped$y
    }

    if (rotate_layout) {
      rotated <- rotate_around_center(node_coords, rotate_angle)
      node_coords$x <- rotated$x
      node_coords$y <- rotated$y
    }


    # Normalize coordinates to center the graph
    node_coords$x <- (node_coords$x - mean(range(node_coords$x))) * relative_x_position + center_x
    node_coords$y <- (node_coords$y - mean(range(node_coords$y))) * relative_y_position + center_y
    node_coords$name <- node_names

    edges_df0 <- data.frame(
      from = edges_data$from,
      to = edges_data$to,
      directed = edges_data$arrow == "last", # directed
      bidirectional = edges_data$arrow == "none", # covariance
      labels = fit_delta_edges$label,
      sig = {
        # Safely check for pval column
        if ("pval" %in% names(edges_data) && !is.null(edges_data$pval)) {
          # Calculate significance, treating NA as FALSE
          ifelse(is.na(edges_data$pval), FALSE, edges_data$pval < 0.05)
        } else {
          # If pval doesn't exist or is NULL, all TRUE (Bayesian)
          rep(TRUE, nrow(edges_data))
        }
      }
    )

    self_loop_indices <- which(edges_df0$from == edges_df0$to)

    if (length(self_loop_indices) > 0) {
      edges_df0$self_loop <- FALSE
      edges_df0$self_loop[self_loop_indices] <- TRUE
    } else {
      edges_df0$self_loop <- FALSE
    }

    edges_df0 <- edges_df0[!duplicated(
      t(apply(edges_df0[c("from", "to")], 1, sort))
    ), ]

    edges_df <- edges_df0[edges_df0$from != edges_df0$to, ]
    edges_loop_df <- edges_df0[edges_df0$self_loop,]

    loop_node_names <- edges_loop_df$from
    loop_node_names <- loop_node_names[order(match(loop_node_names, node_names))]

    edge_op <- ifelse(edges_df$bidirectional, "~~", "~")
    edges_from <- edges_df$from
    edges_to <- edges_df$to
    edge_labels <- edges_df$labels


    node_shapes <- ifelse(node_names %in% intercept_vars, int_shape, # Triangular shape for the intercept node
                          ifelse(node_names %in% latent_vars, latent_shape, observed_shape))
    node_colors <- ifelse(node_names %in% intercept_vars, point_color_int,
                          ifelse(node_names %in% latent_vars, point_color_latent, point_color_observed))
    node_sizes <- ifelse(node_names %in% intercept_vars, point_size_int,
                         ifelse(node_names %in% latent_vars, point_size_latent, point_size_observed))

    node_width_height_ratios <- node_width_height_ratios <- ifelse(node_names %in% intercept_vars, width_height_ratio_int,
                                                                   ifelse(node_names %in% latent_vars, width_height_ratio_latent, width_height_ratio_observed))


  } else {

    stop("Must be output from tidySEM with class of 'sem_graph'.")

  }

  apply_modifications <- function(data, modifications, config, mode, batch_process = FALSE) {
    if (is.null(modifications) || nrow(modifications) == 0) return(data)
    modified_data <- data

    if (batch_process && !is.null(config$batch_special_case)) {
      mods <- modifications
      modified_data <- config$batch_special_case(modified_data, mods)
    } else {

      for (i in seq_len(nrow(modifications))) {
        mod <- modifications[i, ]

        if (mode == 'edge') {
          idx <- which(
            edges_from == mod$lhs &
              edges_to == mod$rhs
          )

        } else if (mode == 'node') {
          idx <- which(
            node_coords$name == mod$text
          )
        } else if (mode == 'loop') {
          idx <- which(
            node_coords$name[!node_coords$name %in% loop_names_remove] == mod$text
          )
        }

        if (length(idx) > 0) {
          for (col in config$modify_cols) {
            if (col %in% names(mod) && col %in% names(modified_data)) {
              modified_data[idx, col] <- mod[[col]]
            }
          }

          if (!is.null(config$special_case)) {
            modified_data <- config$special_case(modified_data, idx, mod)
          }
        }
      }
    }
    return(modified_data)
  }

  if (modify_params_latent_node_angle) {
    node_coords <- apply_modifications(
      node_coords,
      modified_latent_nodes_angle,
      config = list(
        batch_special_case = function(data, mods) {

          latent_positions <- which(mods$node_type == "latent")

          most_recent_groups <- list()

          for (i in seq_along(latent_positions)) {
            current_latent_pos <- latent_positions[i]

            if (i == length(latent_positions) ||
                (current_latent_pos + 1 < latent_positions[i + 1] &&
                 mods$node_type[current_latent_pos + 1] == "observed")) {

              group_rows <- current_latent_pos
              next_row <- current_latent_pos + 1

              while (next_row <= nrow(mods) && mods$node_type[next_row] == "observed") {
                group_rows <- c(group_rows, next_row)
                next_row <- next_row + 1
              }

              group_data <- mods[group_rows, ]
              latent_name <- group_data$text[1]

              if (is.null(most_recent_groups[[latent_name]]) ||
                  group_rows[1] > most_recent_groups[[latent_name]]$positions[1]) {
                most_recent_groups[[latent_name]] <- list(
                  data = group_data,
                  positions = group_rows
                )
              }
            }
          }

          final_mods_list <- lapply(most_recent_groups, function(x) x$data)

          for (i in  seq_along(final_mods_list)) {
            curr_mods <- final_mods_list[[i]]
            latent_mod <- curr_mods[curr_mods$node_type == "latent", ]
            observed_mods <- curr_mods[curr_mods$node_type == "observed", ]

            latent_node_idx <- which(data$name == latent_mod$text) # idx in node_coords
            obs_node_idx <- which(data$name %in% observed_mods$text)

            if (length(latent_node_idx) > 0 && length(obs_node_idx) > 0) {
              # Calculate centroid (latent node xy)
              center_x <- data$x[latent_node_idx]
              center_y <- data$y[latent_node_idx]

              angle_rad <- curr_mods$angle * pi / 180

              for (obs_idx in obs_node_idx) {
                if(length(obs_idx) > 0) {
                  x_relative <- data$x[obs_idx] - center_x
                  y_relative <- data$y[obs_idx] - center_y

                  new_x <- x_relative * cos(angle_rad) - y_relative * sin(angle_rad)
                  new_y <- x_relative * sin(angle_rad) + y_relative * cos(angle_rad)

                  data$x[obs_idx] <- new_x + center_x
                  data$y[obs_idx] <- new_y + center_y
                }
              }
            }
          }

          return(data)
        }
      ),
      mode = 'node', # ignored
      batch_process = TRUE
    )
  }


  # Apply node position modifications
  if (modify_params_node_xy) {
    node_coords <- apply_modifications(
      node_coords,
      modified_nodes_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0), # No direct column mods
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }

  # Apply latent node group position modifications
  if (modify_params_latent_node_xy) {
    node_coords <- apply_modifications(
      node_coords,
      modified_latent_nodes_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0), # No direct column mods
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }

  # Create points dataframe
  points_df <- data.frame(
    x = node_coords$x,
    y = node_coords$y,
    shape = node_shapes,
    color = node_colors,
    size = node_sizes,
    border_color = node_border_color,
    border_width = node_border_width,
    alpha = 1,
    width_height_ratio = node_width_height_ratios,
    orientation = 0,
    lavaan = TRUE,
    network = FALSE,
    locked = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  #Apply node modifications
  if (modify_params_node) {
    points_df <- apply_modifications(
      points_df,
      modified_nodes,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "alpha", "shape", "size", "border_color", "border_width", "width_height_ratio"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }

  # Create annotations
  annotations <- data.frame(
    text = node_coords$name,
    x = node_coords$x,
    y = node_coords$y,
    font = ifelse(node_names %in% latent_vars, text_font_latent, text_font_others),
    size = ifelse(node_names %in% latent_vars, text_size_latent, text_size_others),
    color = ifelse(node_names %in% latent_vars, text_color_latent, text_color_others),
    fill = NA,
    angle = 0,
    alpha = ifelse(node_names %in% latent_vars, text_alpha_latent, text_alpha_others),
    fontface = ifelse(node_names %in% latent_vars, text_fontface_latent, text_fontface_others),
    math_expression = FALSE,
    hjust = 0.5,
    vjust = 0.5,
    lavaan = TRUE,
    network = FALSE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )


  if (modify_params_nodelabel) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "size", "alpha", "angle", "font", "fontface"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }

  if (modify_params_nodelabel_xy) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }


  if (modify_params_nodelabel_text) {
    if (nrow(modified_nodelabels_text) > 0) {
      for (i in seq_len(nrow(modified_nodelabels_text))) {
        mod <- modified_nodelabels_text[i, ]
        node_idx <- which(
          node_coords$name == mod$text
        )
        if (length(node_idx) == 1) {
          annotations$text[[node_idx]] <- mod$nodelabel
          annotations$math_expression[[node_idx]] <- mod$math_expression
        }
      }
    }
  }


  if (length(edges_from) == 0 || length(edges_to) == 0) {
    stop("No edges found in the model. Check the Lavaan syntax.")
  }

  # Create lines dataframe
  lines_df_pre <- data.frame(
    x_start = node_coords[match(edges_from, node_names), "x"],
    y_start = node_coords[match(edges_from, node_names), "y"],
    x_end = node_coords[match(edges_to, node_names), "x"],
    y_end = node_coords[match(edges_to, node_names), "y"],
    ctrl_x = NA,
    ctrl_y = NA,
    ctrl_x2 = NA,
    ctrl_y2 = NA,
    curvature_magnitude = NA,
    rotate_curvature = NA,
    curvature_asymmetry = NA,
    type = ifelse(edge_op == "~~", "Curved Arrow", "Straight Arrow"),
    color = edge_color,
    end_color = NA,
    color_type = "Single",
    gradient_position = NA,
    width = line_width,
    alpha = line_alpha,
    arrow = TRUE,
    arrow_type = arrow_type,
    arrow_size = arrow_size,
    two_way = edge_op == "~~",
    lavaan = TRUE,
    network = FALSE,
    line_style = "solid",
    locked = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  # Apply edge modifications

  edge_sig_idx <- which(edges_df$sig == TRUE)
  non_edge_sig_idx <- which(edges_df$sig == FALSE)

  if (highlight_free_path) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      ff_params_edge,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color")
      ),
      mode = 'edge'
    )
  }

  if (highlight_sig_path) {
    lines_df_pre$color[edge_sig_idx] <- sig_path_color
    lines_df_pre$color[non_edge_sig_idx] <- non_sig_path_color
  }

  if (highlight_free_path_multi_group) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      ff_params_edge_multi,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "width")
      ),
      mode = 'edge'
    )
  }

  if (highlight_multi_group) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      sig_diff_edge,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "width")
      ),
      mode = 'edge'
    )
  }

  if (modify_params_edge) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      modified_edges,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "width", "alpha", "line_style", "end_color", "gradient_position", "color_type")
      ),
      mode = 'edge'
    )
  }

  # Adjust edge coordinates
  edge_list <- cbind(match(edges_from, node_names), match(edges_to, node_names))

  lines_df <- adjust_edge_coordinates(
    lines_df = lines_df_pre,
    edge_list = edge_list,
    points_df = points_df,
    auto_endpoint_spacing = line_endpoint_spacing
  )

  if ("two_way" %in% colnames(lines_df) && any(lines_df$two_way, na.rm = TRUE)) {
    lines_df[lines_df$two_way, c("x_start", "x_end", "y_start", "y_end")] <-
      lines_df[lines_df$two_way, c("x_start", "x_end", "y_start", "y_end")] +
      c(lavaan_curved_x_shift, lavaan_curved_x_shift, lavaan_curved_y_shift, lavaan_curved_y_shift)
  }



  if (any(lines_df$two_way)) {
    two_way_indices <- which(lines_df$two_way)
    control_points <- mapply(
      calculate_control_point,
      x_start = lines_df$x_start[two_way_indices],
      y_start = lines_df$y_start[two_way_indices],
      x_end = lines_df$x_end[two_way_indices],
      y_end = lines_df$y_end[two_way_indices],
      curvature_magnitude = lavaan_curvature_magnitude,
      rotate_curvature = lavaan_rotate_curvature,
      curvature_asymmetry = lavaan_curvature_asymmetry,
      center_x = mean(node_coords$x),
      center_y = mean(node_coords$y),
      SIMPLIFY = FALSE
    )
    lines_df$ctrl_x[two_way_indices] <- sapply(control_points, `[[`, "ctrl_x")
    lines_df$ctrl_y[two_way_indices] <- sapply(control_points, `[[`, "ctrl_y")
    lines_df$ctrl_x2[two_way_indices] <- sapply(control_points, `[[`, "ctrl_x2")
    lines_df$ctrl_y2[two_way_indices] <- sapply(control_points, `[[`, "ctrl_y2")

    lines_df$curvature_magnitude[two_way_indices] <- lavaan_curvature_magnitude
    lines_df$rotate_curvature[two_way_indices] <- lavaan_rotate_curvature
    lines_df$curvature_asymmetry[two_way_indices] <- lavaan_curvature_asymmetry
  }

  # Apply covariance edge modifications
  if (modify_params_cov_edge) {
    lines_df <- apply_modifications(
      lines_df,
      modified_cov_edges,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = NULL,
        special_case = function(data, idx, mod) {
          # Add input validation
          if (length(idx) == 1 &&
              all(c("x_start", "y_start", "x_end", "y_end") %in% names(data)) &&
              all(c("curvature_magnitude", "rotate_curvature", "curvature_asymmetry", "x_shift", "y_shift") %in% names(mod))) {

            data$x_start[idx] <- data$x_start[idx] + mod$x_shift
            data$x_end[idx] <- data$x_end[idx] + mod$x_shift
            data$y_start[idx] <- data$y_start[idx] + mod$y_shift
            data$y_end[idx] <- data$y_end[idx] + mod$y_shift

            cp <- calculate_control_point(
              x_start = data$x_start[idx],
              y_start = data$y_start[idx],
              x_end = data$x_end[idx],
              y_end = data$y_end[idx],
              curvature_magnitude = mod$curvature_magnitude,
              rotate_curvature = mod$rotate_curvature,
              curvature_asymmetry = mod$curvature_asymmetry,
              center_x = mean(node_coords$x),
              center_y = mean(node_coords$y)
            )

            # Safely assign control points
            if (all(c("ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2", "locked") %in% names(data))) {
              data$ctrl_x[idx] <- cp$ctrl_x
              data$ctrl_y[idx] <- cp$ctrl_y
              data$ctrl_x2[idx] <- cp$ctrl_x2
              data$ctrl_y2[idx] <- cp$ctrl_y2
              data$curvature_magnitude[idx] <- mod$curvature_magnitude
              data$rotate_curvature[idx] <- mod$rotate_curvature
              data$curvature_asymmetry[idx] <- mod$curvature_asymmetry
              data$locked[idx] <- FALSE
            }
          }
          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  lines_df$type[lines_df$curvature_magnitude == 0] <- "Straight Arrow"
  lines_df$type[lines_df$curvature_magnitude != 0] <- "Curved Arrow"

  if (modify_params_edge_xy) {
    lines_df <- apply_modifications(
      lines_df,
      modified_edges_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x_start[idx] <- mod$start_x_shift # data$x_start[idx] + mod$start_x_shift
          data$y_start[idx] <- mod$start_y_shift # data$y_start[idx] + mod$start_y_shift
          data$x_end[idx] <- mod$end_x_shift # data$x_end[idx] + mod$end_x_shift
          data$y_end[idx] <- mod$end_y_shift # data$y_end[idx] + mod$end_y_shift

          if (data$type[idx] %in% c('Curved Line', 'Curved Arrow')) {
            cp <- calculate_control_point(
              x_start = data$x_start[idx],
              y_start = data$y_start[idx],
              x_end = data$x_end[idx],
              y_end = data$y_end[idx],
              curvature_magnitude = data$curvature_magnitude[idx],
              rotate_curvature = data$rotate_curvature[idx],
              curvature_asymmetry = data$curvature_asymmetry[idx],
              center_x = mean(node_coords$x),
              center_y = mean(node_coords$y)
            )

            data$ctrl_x[idx] <- cp$ctrl_x
            data$ctrl_y[idx] <- cp$ctrl_y
            data$ctrl_x2[idx] <- cp$ctrl_x2
            data$ctrl_y2[idx] <- cp$ctrl_y2
          }

          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  # Handle arrow location
  if (exists("lavaan_arrow_location") && lavaan_arrow_location == "start") {
    temp <- lines_df[, c("x_start", "y_start")]
    lines_df[, c("x_start", "y_start")] <- lines_df[, c("x_end", "y_end")]
    lines_df[, c("x_end", "y_end")] <- temp
  }

  if (!is.null(loop_names_remove)) {
    loop_node_names <- loop_node_names[!loop_node_names %in% loop_names_remove]
  }


  if (residuals) {
    loops_df = data.frame(
      x_center = node_coords[match(loop_node_names, node_names), "x"],
      y_center = node_coords[match(loop_node_names, node_names), "y"],
      radius = lavaan_radius,
      color = lavaan_line_color_loop,
      width = lavaan_width_loop,
      alpha = lavaan_line_alpha_loop,
      arrow_type = lavaan_arrow_type_loop,
      arrow_size = lavaan_arrow_size_loop,
      gap_size = lavaan_gap_size_loop,
      loop_width = lavaan_width_loop,
      loop_height = lavaan_height_loop,
      orientation = 0, # later modified
      lavaan = TRUE,
      two_way = lavaan_two_way_arrow_loop,
      locked = FALSE,
      group = which_group,
      stringsAsFactors = FALSE
    )

    offset_distance <- lavaan_loop_offset

    for (i in 1:nrow(loops_df)) {
      node_name <- loop_node_names[i]
      node_index <- which(node_names == node_name)

      node_x <- points_df$x[node_index]
      node_y <- points_df$y[node_index]
      node_shape <- points_df$shape[node_index]
      node_size <- points_df$size[node_index]
      node_ratio <- points_df$width_height_ratio[node_index]
      node_orientation <- points_df$orientation[node_index]

      connected_edges <- lines_df[lines_df$edges_from == node_name | lines_df$edges_to == node_name, ]

      if (nrow(connected_edges) > 0) {
        edge_vectors <- list()

        for (j in 1:nrow(connected_edges)) {
          if (connected_edges$edges_from[j] == node_name) {
            # Outgoing edge
            dx <- connected_edges$x_end[j] - connected_edges$x_start[j]
            dy <- connected_edges$y_end[j] - connected_edges$y_start[j]
          } else {
            # Incoming edge (reverse direction)
            dx <- connected_edges$x_start[j] - connected_edges$x_end[j]
            dy <- connected_edges$y_start[j] - connected_edges$y_end[j]
          }
          edge_vectors[[j]] <- c(dx, dy)
        }

        avg_dx <- mean(sapply(edge_vectors, function(v) v[1]))
        avg_dy <- mean(sapply(edge_vectors, function(v) v[2]))

        angle_to_connections <- atan2(avg_dy, avg_dx) * 180 / pi
        gap_angle <- (angle_to_connections + 180) %% 360  # Opposite direction

      } else {
        dx_to_center <- mean(node_coords$x) - node_x
        dy_to_center <- mean(node_coords$y) - node_y
        gap_angle <- atan2(dy_to_center, dx_to_center) * 180 / pi
        gap_angle <- ifelse(gap_angle < 0, gap_angle + 360, gap_angle)
      }

      if (residuals_orientation_type == 'Quadratic') {
        quadrant_angles <- c(0, 90, 180, 270)
        angle_differences <- abs(gap_angle - quadrant_angles)
        angle_differences <- pmin(angle_differences, 360 - angle_differences)
        nearest_quadrant <- quadrant_angles[which.min(angle_differences)]
        final_gap_angle <- nearest_quadrant
      } else {
        final_gap_angle <- gap_angle
      }

      loops_df$orientation[i] <- (final_gap_angle - 90) %% 360

      alignment <- detect_local_alignment(node_x, node_y, node_coords$x, node_coords$y)

      if (alignment$type == "horizontal" && alignment$count >= 2) {
        group_dx <- 0
        group_dy <- ifelse(node_y > mean(node_coords$y), 1, -1)  # Outer side
      } else if (alignment$type == "vertical" && alignment$count >= 2) {
        group_dx <- ifelse(node_x > mean(node_coords$x), 1, -1)  # Outer side
        group_dy <- 0
      } else {
        position_angle <- (final_gap_angle + 180) %% 360
        group_dx <- cos(position_angle * pi / 180)
        group_dy <- sin(position_angle * pi / 180)
      }

      boundary_point <- find_intersection(
        x_center = node_x,
        y_center = node_y,
        x_target = node_x + group_dx,
        y_target = node_y + group_dy,
        size = node_size,
        width_height_ratio = node_ratio,
        orientation = node_orientation,
        shape = node_shape
      )

      dx_boundary <- boundary_point$x - node_x
      dy_boundary <- boundary_point$y - node_y
      distance_to_boundary <- sqrt(dx_boundary^2 + dy_boundary^2)

      if (distance_to_boundary > 0) {
        scale <- (distance_to_boundary + offset_distance) / distance_to_boundary
        loops_df$x_center[i] <- node_x + dx_boundary * scale
        loops_df$y_center[i] <- node_y + dy_boundary * scale
      } else {
        loops_df$x_center[i] <- node_x + group_dx * offset_distance
        loops_df$y_center[i] <- node_y + group_dy * offset_distance
      }

      loop_x <- loops_df$x_center[i]
      loop_y <- loops_df$y_center[i]

      dx_loop_to_node <- node_x - loop_x
      dy_loop_to_node <- node_y - loop_y

      angle_loop_to_node <- atan2(dy_loop_to_node, dx_loop_to_node) * 180 / pi
      angle_loop_to_node <- ifelse(angle_loop_to_node < 0, angle_loop_to_node + 360, angle_loop_to_node)

      loops_df$orientation[i] <- (angle_loop_to_node - 90) %% 360
    }

  } else {
    loops_df = data.frame(
      x_center = numeric(), y_center = numeric(), radius = numeric(), color = character(),
      width = numeric(), alpha = numeric(), arrow_type = character(), arrow_size = numeric(),
      gap_size = numeric(), loop_width = numeric(), loop_height = numeric(), orientation = numeric(),
      lavaan = logical(), two_way = logical(), locked = logical(), group = character(), stringsAsFactors = FALSE
    )
  }

  if (residuals) {

    if (highlight_free_path) {
      loops_df <- apply_modifications(
        loops_df,
        ff_params_loop,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color")
        ),
        mode = 'loop'
      )
    }

    loop_sig_idx <- which(edges_loop_df$sig == TRUE)
    non_loop_sig_idx <- which(edges_loop_df$sig == FALSE)

    if (highlight_sig_path) {
      loops_df$color[loop_sig_idx] <- sig_path_color
      loops_df$color[non_loop_sig_idx] <- non_sig_path_color
    }

    if (highlight_free_path_multi_group) {
      loops_df <- apply_modifications(
        loops_df,
        ff_params_loop_multi,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "width", "radius")
        ),
        mode = 'loop'
      )
    }

    if (highlight_multi_group) {
      loops_df <- apply_modifications(
        loops_df,
        sig_diff_loop,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "width"),
          special_case = NULL
        ),
        mode = 'loop'
      )
    }

    if (modify_params_loop) {
      loops_df <- apply_modifications(
        loops_df,
        modified_loops,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "alpha", "radius", "width", "type", "arrow_size", "gap_size", "two_way"),
          special_case = NULL
        ),
        mode = 'loop'
      )
    }

    if (modify_params_loop_xy) {
      loops_df <- apply_modifications(
        loops_df,
        modified_loops_xy,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = character(0), # No direct column mods
          special_case = function(data, idx, mod) {
            data$x_center[idx] <- data$x_center[idx] + mod$x_shift
            data$y_center[idx] <- data$y_center[idx] + mod$y_shift
            return(data)
          }
        ),
        mode = 'loop'
      )
    }

    if (modify_params_loop_location) {
      loops_df <- apply_modifications(
        loops_df,
        modified_loops_location,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = character(0),
          special_case = function(data, idx, mod) {
            # For each modified loop, recompute position and orientation
            for (i in idx) {
              node_name <- loop_node_names[i]
              node_index <- which(node_names == node_name)

              node_x <- points_df$x[node_index]
              node_y <- points_df$y[node_index]
              node_shape <- points_df$shape[node_index]
              node_size <- points_df$size[node_index]
              node_ratio <- points_df$width_height_ratio[node_index]
              node_orientation <- points_df$orientation[node_index]

              loop_angle <- as.numeric(mod$loop_location)

              angle_rad <- loop_angle * pi / 180
              group_dx <- cos(angle_rad)
              group_dy <- sin(angle_rad)

              # Find boundary point and apply offset
              boundary_point <- find_intersection(
                x_center = node_x,
                y_center = node_y,
                x_target = node_x + group_dx,
                y_target = node_y + group_dy,
                size = node_size,
                width_height_ratio = node_ratio,
                orientation = node_orientation,
                shape = node_shape
              )

              offset_distance <- lavaan_loop_offset

              dx_boundary <- boundary_point$x - node_x
              dy_boundary <- boundary_point$y - node_y
              distance_to_boundary <- sqrt(dx_boundary^2 + dy_boundary^2)

              # Update loop position
              if (distance_to_boundary > 0) {
                scale <- (distance_to_boundary + offset_distance) / distance_to_boundary
                data$x_center[i] <- node_x + dx_boundary * scale
                data$y_center[i] <- node_y + dy_boundary * scale
              } else {
                data$x_center[i] <- node_x + group_dx * offset_distance
                data$y_center[i] <- node_y + group_dy * offset_distance
              }

              # Recalculate orientation based on new position
              loop_x <- data$x_center[i]
              loop_y <- data$y_center[i]

              dx_loop_to_node <- node_x - loop_x
              dy_loop_to_node <- node_y - loop_y

              angle_loop_to_node <- atan2(dy_loop_to_node, dx_loop_to_node) * 180 / pi
              angle_loop_to_node <- ifelse(angle_loop_to_node < 0, angle_loop_to_node + 360, angle_loop_to_node)

              data$orientation[i] <- (angle_loop_to_node - 90) %% 360
            }

            return(data)
          }
        ),
        mode = 'loop'
      )
    }
  }

  # Prepare edge labels
  lines_df0 <- cbind(lines_df, from = edges_from, to = edges_to, text = edge_labels)
  edgelabels_xy_df <- data.frame(x = numeric(nrow(lines_df0)), y = numeric(nrow(lines_df0)))

  for (i in seq_len(nrow(lines_df0))) {
    intp_points <- if (lines_df0$type[i] == "Curved Arrow") {
      create_bezier_curve(
        x_start = lines_df0$x_start[i], y_start = lines_df0$y_start[i],
        x_end = lines_df0$x_end[i], y_end = lines_df0$y_end[i],
        ctrl_x = lines_df0$ctrl_x[i], ctrl_y = lines_df0$ctrl_y[i],
        ctrl_x2 = lines_df0$ctrl_x2[i], ctrl_y2 = lines_df0$ctrl_y2[i], n_points = 100
      )
    } else {
      interpolate_points(
        x_start = lines_df0$x_start[i], y_start = lines_df0$y_start[i],
        x_end = lines_df0$x_end[i], y_end = lines_df0$y_end[i], n = 100
      )
    }

    mid_idx <- ifelse(lines_df0$type[i] == "Curved Arrow",
                      find_peak_point(
                        intp_points,
                        x_start = lines_df0$x_start[i],
                        y_start = lines_df0$y_start[i],
                        x_end = lines_df0$x_end[i],
                        y_end = lines_df0$y_end[i]
                      ),
                      50)
    #mid_idx <- 50
    edgelabels_xy_df[i, ] <- intp_points[mid_idx, c("x", "y")]
  }

  pval_idx <- grep("\\*", lines_df0$text)

  label_coords <- data.frame(
    text = lines_df0$text,
    x = edgelabels_xy_df$x,
    y = edgelabels_xy_df$y,
    font = text_font_edges,
    size = text_size_edges,
    color = text_color_edges,
    fill = ifelse(lines_df0$text == "", NA, text_color_fill),
    angle = 0,
    alpha = text_alpha_edges,
    fontface = text_fontface_edges,
    math_expression = FALSE,
    hjust = 0.5,
    vjust = 0.5,
    lavaan = TRUE,
    network = FALSE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  ) |>
    filter(nzchar(trimws(text)))

  if (highlight_sig_path) {
    label_coords$fontface[edge_sig_idx] <- sig_label_fontface
    label_coords$fontface[non_edge_sig_idx] <- non_sig_label_fontface
    label_coords$color[edge_sig_idx] <- sig_path_color
    label_coords$color[non_edge_sig_idx] <- non_sig_path_color
  }

  if (highlight_free_path) {
    label_coords <- apply_modifications(
      label_coords,
      ff_params_edgelabel,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (highlight_free_path_multi_group) {
    label_coords <- apply_modifications(
      label_coords,
      ff_params_edgelabel_multi,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (highlight_multi_group) {
    label_coords <- apply_modifications(
      label_coords,
      sig_diff_edgelabel,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fontface")
      ),
      mode = 'edge'
    )
  }

  # Apply edge label modifications
  if (modify_params_edgelabel) {
    label_coords <- apply_modifications(
      label_coords,
      modified_edgelabels,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fill", "size", "alpha", "angle", "font", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (modify_params_edgelabel_xy) {
    label_coords <- apply_modifications(
      label_coords,
      modified_edgelabels_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  if (modify_params_edgelabel_text) {
    if (nrow(modified_edgelabels_text) > 0) {
      for (i in seq_len(nrow(modified_edgelabels_text))) {
        mod <- modified_edgelabels_text[i, ]
        edge_idx <- which(
          edges_from == mod$lhs &
            edges_to == mod$rhs
        )
        if (length(edge_idx) == 1) {
          label_coords$text[[edge_idx]] <- mod$text
          label_coords$math_expression[[edge_idx]] <- mod$math_expression
        }
      }
    }
  }


  if (residuals) {
    # Prepare loop labels
    loop_labels_df <- data.frame(
      x = numeric(nrow(loops_df)),
      y = numeric(nrow(loops_df)),
      text = character(nrow(loops_df)),
      stringsAsFactors = FALSE
    )

    for (i in 1:nrow(loops_df)) {
      node_name <- loop_node_names[i]

      loop_label_row <- edges_loop_df[edges_loop_df$from == node_name & edges_loop_df$self_loop == TRUE, ]

      if (nrow(loop_label_row) > 0) {
        loop_labels_df$text[i] <- as.character(loop_label_row$labels)
      } else {
        loop_labels_df$text[i] <- ""
      }

      node_x <- loops_df$x_center[i]  # Current loop center (after positioning)
      node_y <- loops_df$y_center[i]
      loop_radius <- loops_df$radius[i]
      loop_orientation <- loops_df$orientation[i]

      gap_angle <- (loop_orientation + 90) %% 360
      label_angle <- (gap_angle + 180) %% 360  # Opposite side

      label_angle_rad <- label_angle * pi / 180

      loop_labels_df$x[i] <- node_x + loop_radius * cos(label_angle_rad)
      loop_labels_df$y[i] <- node_y + loop_radius * sin(label_angle_rad)
    }

    loop_label_coords <- data.frame(
      text = loop_labels_df$text,
      x = loop_labels_df$x,
      y = loop_labels_df$y,
      font = text_font_edges,
      size = text_size_edges,
      color = text_color_edges,
      fill = ifelse(loop_labels_df$text == "", NA, text_color_fill),
      angle = 0,
      alpha = text_alpha_edges,
      fontface = text_fontface_edges,
      math_expression = FALSE,
      hjust = 0.5,
      vjust = 0.5,
      lavaan = TRUE,
      network = FALSE,
      locked = FALSE,
      group_label = FALSE,
      loop_label = TRUE,
      group = which_group,
      stringsAsFactors = FALSE
    ) |>
      filter(nzchar(trimws(text)))
  } else {
    loop_label_coords <- data.frame(
      text = character(), x = numeric(), y = numeric(), font = character(), size = numeric(), color = character(), fill = character(), angle = numeric(), alpha = numeric(),
      fontface = character(), math_expression = logical(), hjust = numeric(), vjust = numeric(), lavaan = logical(), network = logical(), locked = logical(), group_label = logical(), loop_label = logical(), group = character(),
      stringsAsFactors = FALSE
    )
  }


  if (residuals) {

    if (highlight_free_path) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        ff_params_looplabel,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "fontface")
        ),
        mode = 'loop'
      )
    }

    if (highlight_sig_path) {
      loop_label_coords$fontface[loop_sig_idx] <- sig_label_fontface
      loop_label_coords$fontface[non_loop_sig_idx] <- non_sig_label_fontface
      loop_label_coords$color[loop_sig_idx] <- sig_path_color
      loop_label_coords$color[non_loop_sig_idx] <- non_sig_path_color
    }

    if (highlight_free_path_multi_group) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        ff_params_looplabel_multi,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "fontface")
        ),
        mode = 'loop'
      )
    }

    if (highlight_multi_group) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        sig_diff_looplabel,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "fontface")
        ),
        mode = 'loop'
      )
    }

    if (modify_params_looplabel) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        modified_looplabels,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "size", "alpha", "angle", "font", "fontface"),
          special_case = NULL
        ),
        mode = 'loop'
      )
    }

    if (modify_params_looplabel_xy) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        modified_looplabels_xy,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = character(0),
          special_case = function(data, idx, mod) {
            data$x[idx] <- data$x[idx] + mod$x_shift
            data$y[idx] <- data$y[idx] + mod$y_shift
            return(data)
          }
        ),
        mode = 'loop'
      )
    }

    if (modify_params_looplabel_text) {
      if (nrow(modified_looplabels_text) > 0) {
        for (i in seq_len(nrow(modified_looplabels_text))) {
          mod <- modified_looplabels_text[i, ]
          loop_idx <- which(
            node_coords$name[!node_coords$name %in% loop_names_remove] == mod$text
          )
          if (length(loop_idx) == 1) {
            loop_label_coords$text[[loop_idx]] <- mod$looplabel
            loop_label_coords$math_expression[[loop_idx]] <- mod$math_expression
          }
        }
      }
    }
  }

  if (remove_edgelabels) {
    label_coords <- data.frame(
      text = character(),
      x = numeric(),
      y = numeric(),
      font = character(),
      size = numeric(),
      color = character(),
      fill = character(),
      angle = numeric(),
      alpha = numeric(),
      fontface = character(),
      math_expression = logical(),
      hjust = numeric(),
      vjust = numeric(),
      lavaan = logical(),
      network = logical(),
      locked = logical(),
      group_label = logical(),
      loop_label = logical(),
      group = character(),
      stringsAsFactors = FALSE
    )
  }

  if (!is.null(data_file)) {
    if (data_file) {
      annotations <- rbind(annotations, label_coords, loop_label_coords)
    }
  }

  points_df[c("x", "y")] <- lapply(points_df[c("x", "y")], round, 5)

  line_cols <- c("x_start", "y_start", "x_end", "y_end",
                 "ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2")
  lines_df[line_cols] <- lapply(lines_df[line_cols], round, 5)

  annotations[c("x", "y")] <- lapply(annotations[c("x", "y")], round, 5)

  loops_df[c("x_center", "y_center")] <- lapply(loops_df[c("x_center", "y_center")], round, 5)

  list(points = points_df, lines = lines_df, annotations = annotations, loops = loops_df)
}

generate_graph_from_diagrammeR <- function(fit, relative_x_position = 25, relative_y_position = 25,
                                           center_x = 0, center_y = 0,
                                           latent_shape = "circle", observed_shape = "square",
                                           int_shape = "triangle",
                                           point_size_latent = 20, point_size_observed = 12,
                                           point_size_int = 10,
                                           width_height_ratio_latent = 1,
                                           width_height_ratio_observed = 1,
                                           width_height_ratio_int = 1,
                                           line_width = 1, line_alpha = 1, text_size_latent = 18, text_font_latent = "sans",
                                           text_color_latent = "#FFFFFF", text_alpha_latent = 1, text_fontface_latent = 'plain',
                                           text_size_others = 16, text_font_others = "sans",
                                           text_color_others = "#FFFFFF", text_alpha_others = 1, text_fontface_others = 'plain',
                                           text_size_edges = 14, text_font_edges = "sans",
                                           text_color_edges =  "#000000", text_color_fill = "#FFFFFF", text_alpha_edges = 1, text_fontface_edges = 'plain',
                                           point_color_latent = "#cc3d3d", point_color_observed = "#1262b3",
                                           point_color_int = "#0f993d",
                                           edge_color = "#000000", line_endpoint_spacing = 0.2,
                                           node_border_color = "#FFFFFF",
                                           node_border_width = 1,
                                           arrow_type = "closed", arrow_size = 0.1,
                                           lavaan_arrow_location = "end",
                                           zoom_factor = 1.2,
                                           lavaan_curvature_magnitude = 0.5,
                                           lavaan_rotate_curvature = FALSE,
                                           lavaan_curvature_asymmetry = 0,
                                           lavaan_curved_x_shift = 0,
                                           lavaan_curved_y_shift = 0,
                                           remove_edgelabels = FALSE,
                                           highlight_free_path = FALSE,
                                           ff_params_edge = NULL,
                                           ff_params_edgelabel = NULL,
                                           highlight_sig_path = FALSE,
                                           sig_path_color = "#000000",
                                           non_sig_path_color = "#000000",
                                           sig_label_fontface = "plain",
                                           non_sig_label_fontface = "plain",
                                           data_file = NULL,
                                           modify_params_edge = FALSE,
                                           modified_edges = NULL,
                                           modify_params_edgelabel = FALSE,
                                           modified_edgelabels = NULL,
                                           modify_params_edgelabel_xy = FALSE,
                                           modified_edgelabels_xy = NULL,
                                           modify_params_edgelabel_text = FALSE,
                                           modified_edgelabels_text = NULL,
                                           modify_params_node = FALSE,
                                           modified_nodes = NULL,
                                           modify_params_node_xy = FALSE,
                                           modified_nodes_xy = NULL,
                                           modify_params_edge_xy = FALSE,
                                           modified_edges_xy = NULL,
                                           modify_params_cov_edge = FALSE,
                                           modified_cov_edges = NULL,
                                           modify_params_nodelabel = FALSE,
                                           modified_nodelabels = NULL,
                                           modify_params_nodelabel_xy = FALSE,
                                           modified_nodelabels_xy = NULL,
                                           modify_params_nodelabel_text = FALSE,
                                           modified_nodelabels_text = NULL,
                                           modify_params_latent_node_xy = FALSE,
                                           modified_latent_nodes_xy = NULL,
                                           modify_params_latent_node_angle = FALSE,
                                           modified_latent_nodes_angle = NULL,
                                           apply_global_nodes = FALSE,
                                           apply_global_edges = FALSE,
                                           apply_global_annotations = FALSE,
                                           flip_layout = FALSE,
                                           flip_direction = NULL,
                                           rotate_layout = FALSE,
                                           rotate_angle = 0,
                                           which_group = "1") {

  apply_modifications <- function(data, modifications, config, mode, batch_process = FALSE) {
    if (is.null(modifications) || nrow(modifications) == 0) return(data)
    modified_data <- data

    if (batch_process && !is.null(config$batch_special_case)) {
      mods <- modifications
      modified_data <- config$batch_special_case(modified_data, mods)
    } else {
      for (i in seq_len(nrow(modifications))) {
        mod <- modifications[i, ]

        if (mode == 'edge') {
          idx <- which(
            edges_from == mod$lhs &
              edges_to == mod$rhs
          )
        } else if (mode == 'node') {
          idx <- which(
            node_coords$name == mod$text
          )
        }

        if (length(idx) > 0) {
          for (col in config$modify_cols) {
            if (col %in% names(mod) && col %in% names(modified_data)) {
              modified_data[idx, col] <- mod[[col]]
            }
          }

          if (!is.null(config$special_case)) {
            modified_data <- config$special_case(modified_data, idx, mod)
          }
        }
      }
    }
    return(modified_data)
  }

  if (inherits(fit, "grViz")) {
    dot_code <- fit$x$diagram
    node_matches <- extract_node_matches(dot_code)

    node_df <- data.frame(
      id = node_matches[, 1],
      attrs = node_matches[, 2],
      stringsAsFactors = FALSE
    ) |>
      filter(nchar(attrs) > 0)  |>
      mutate(
        attrs = map(attrs, ~str_split(.x, ",\\s*")[[1]] |>
                      discard(~.x == ""))  # Remove empty attributes
      ) |>
      unnest(attrs) |>
      mutate(
        # Handle cases where '=' might be missing
        attr_val = ifelse(grepl("=", attrs), attrs, paste0(attrs, "=TRUE"))
      ) |>
      separate(attr_val, into = c("attr", "value"), sep = "=", extra = "merge")

    edge_df <- dot_code |>
      str_extract_all("[\"']?(\\w+)[\"']?\\s*->\\s*[\"']?(\\w+)[\"']?", simplify = TRUE) |>
      as.data.frame() |>
      setNames(c("full", "from", "to"))

    svg_data <- export_svg(fit)
    svg <- read_xml(svg_data)
    xml_ns_strip(svg)
    nodes <- xml_find_all(svg, "//g[contains(@class, 'node')]")
    edges <- xml_find_all(svg, "//g[contains(@class, 'edge')]")

    nodes_df0 <- bind_rows(lapply(nodes, extract_node_properties, svg))

    edges_df0 <- bind_rows(lapply(edges, extract_edge_properties, svg))
    edges_df0 <- edges_df0 |>
      mutate(across(c(label_x, label_y), ~abs(.x)))

    element_max <- max(c(length(unique(nodes_df0$x)), length(unique(nodes_df0$y))))

    scaled_data <- scale_coordinates_centered(nodes_df0, edges_df0, new_min = -0.15 * element_max, new_max = 0.15 * element_max)

    nodes_df <- scaled_data$nodes
    edges_df <- scaled_data$edges

    node_coords <- data.frame(x = nodes_df$x, y = nodes_df$y)

    if (flip_layout) {
      flipped <- flip_around_center(node_coords, flip_direction)
      node_coords$x <- flipped$x
      node_coords$y <- flipped$y
    }

    if (rotate_layout) {
      rotated <- rotate_around_center(node_coords, rotate_angle)
      node_coords$x <- rotated$x
      node_coords$y <- rotated$y
    }

    node_coords$x <- (node_coords$x - mean(range(node_coords$x))) * relative_x_position + center_x
    node_coords$y <- (node_coords$y - mean(range(node_coords$y))) * relative_y_position + center_y
    node_coords$name <- nodes_df$label

    name_to_label <- setNames(nodes_df$label, nodes_df$node_name)

    edges_df$source <- name_to_label[edges_df$source]
    edges_df$target <- name_to_label[edges_df$target]
    edges_from <- edges_df$source
    edges_to <- edges_df$target

    node_names <- nodes_df$label

    latent_vars <- node_names[nodes_df$shape == 'oval']
    observed_vars <- node_names[nodes_df$shape == 'rectangle']
    intercept_vars <- node_names[nodes_df$shape == "triangle"]

    node_shapes <- ifelse(node_names %in% intercept_vars, int_shape, # Triangular shape for the intercept node
                          ifelse(node_names %in% latent_vars, latent_shape, observed_shape))
    node_colors <- ifelse(node_names %in% intercept_vars, point_color_int,
                          ifelse(node_names %in% latent_vars, point_color_latent, point_color_observed))
    node_sizes <- ifelse(node_names %in% intercept_vars, point_size_int,
                         ifelse(node_names %in% latent_vars, point_size_latent, point_size_observed))

    node_width_height_ratios <- ifelse(node_names %in% intercept_vars, width_height_ratio_int,
                                       ifelse(node_names %in% latent_vars, width_height_ratio_latent, width_height_ratio_observed))

  } else {
    stop("Must be output from 'DiagrammeR' (grViz object class).")
  }


  if (modify_params_latent_node_angle) {
    node_coords <- apply_modifications(
      node_coords,
      modified_latent_nodes_angle,
      config = list(
        batch_special_case = function(data, mods) {

          latent_positions <- which(mods$node_type == "latent")

          most_recent_groups <- list()

          for (i in seq_along(latent_positions)) {
            current_latent_pos <- latent_positions[i]

            if (i == length(latent_positions) ||
                (current_latent_pos + 1 < latent_positions[i + 1] &&
                 mods$node_type[current_latent_pos + 1] == "observed")) {

              group_rows <- current_latent_pos
              next_row <- current_latent_pos + 1

              while (next_row <= nrow(mods) && mods$node_type[next_row] == "observed") {
                group_rows <- c(group_rows, next_row)
                next_row <- next_row + 1
              }

              group_data <- mods[group_rows, ]
              latent_name <- group_data$text[1]

              if (is.null(most_recent_groups[[latent_name]]) ||
                  group_rows[1] > most_recent_groups[[latent_name]]$positions[1]) {
                most_recent_groups[[latent_name]] <- list(
                  data = group_data,
                  positions = group_rows
                )
              }
            }
          }

          final_mods_list <- lapply(most_recent_groups, function(x) x$data)

          for (i in  seq_along(final_mods_list)) {
            curr_mods <- final_mods_list[[i]]
            latent_mod <- curr_mods[curr_mods$node_type == "latent", ]
            observed_mods <- curr_mods[curr_mods$node_type == "observed", ]

            latent_node_idx <- which(data$name == latent_mod$text) # idx in node_coords
            obs_node_idx <- which(data$name %in% observed_mods$text)

            if (length(latent_node_idx) > 0 && length(obs_node_idx) > 0) {
              # Calculate centroid (latent node xy)
              center_x <- data$x[latent_node_idx]
              center_y <- data$y[latent_node_idx]

              angle_rad <- curr_mods$angle * pi / 180

              for (obs_idx in obs_node_idx) {
                if(length(obs_idx) > 0) {
                  x_relative <- data$x[obs_idx] - center_x
                  y_relative <- data$y[obs_idx] - center_y

                  new_x <- x_relative * cos(angle_rad) - y_relative * sin(angle_rad)
                  new_y <- x_relative * sin(angle_rad) + y_relative * cos(angle_rad)

                  data$x[obs_idx] <- new_x + center_x
                  data$y[obs_idx] <- new_y + center_y
                }
              }
            }
          }

          return(data)
        }
      ),
      mode = 'node', # ignored
      batch_process = TRUE
    )
  }

  # Apply node position modifications
  if (modify_params_node_xy) {
    node_coords <- apply_modifications(
      node_coords,
      modified_nodes_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0), # No direct column mods
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }

  # Apply latent node group position modifications
  if (modify_params_latent_node_xy) {
    node_coords <- apply_modifications(
      node_coords,
      modified_latent_nodes_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0), # No direct column mods
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }

  # Create points dataframe
  points_df <- data.frame(
    x = node_coords$x,
    y = node_coords$y,
    shape = if (apply_global_nodes) node_shapes else nodes_df$shape,
    color = if (apply_global_nodes) node_colors else nodes_df$fill,
    size = node_sizes,
    border_color = if (apply_global_nodes) node_border_color else nodes_df$stroke_color,
    border_width = node_border_width,
    alpha = if (apply_global_nodes) 1 else nodes_df$alpha,
    width_height_ratio = if (apply_global_nodes) node_width_height_ratios else nodes_df$width / nodes_df$height,
    orientation = 0,
    lavaan = TRUE,
    network = FALSE,
    locked = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  #Apply node modifications
  if (modify_params_node) {
    points_df <- apply_modifications(
      points_df,
      modified_nodes,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "alpha", "shape", "size", "border_color", "border_width", "width_height_ratio"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }

  # Create annotations
  annotations <- data.frame(
    text = node_coords$name,
    x = node_coords$x,
    y = node_coords$y,
    font = ifelse(node_names %in% latent_vars, text_font_latent, text_font_others),
    size = if (apply_global_annotations) ifelse(node_names %in% latent_vars, text_size_latent, text_size_others) else nodes_df$font_size,
    color = if (apply_global_annotations) ifelse(node_names %in% latent_vars, text_color_latent, text_color_others) else nodes_df$text_color,
    fill = NA,
    angle = 0,
    alpha = if (apply_global_annotations) ifelse(node_names %in% latent_vars, text_alpha_latent, text_alpha_others) else 1,
    fontface = if (apply_global_annotations) ifelse(node_names %in% latent_vars, text_fontface_latent, text_fontface_others) else 'plain',
    math_expression = FALSE,
    hjust = 0.5,
    vjust = 0.5,
    lavaan = TRUE,
    network = FALSE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  if (modify_params_nodelabel) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "size", "alpha", "angle", "font", "fontface"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }


  if (modify_params_nodelabel_xy) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }

  if (modify_params_nodelabel_text) {
    if (nrow(modified_nodelabels_text) > 0) {
      for (i in seq_len(nrow(modified_nodelabels_text))) {
        mod <- modified_nodelabels_text[i, ]
        node_idx <- which(
          node_coords$name == mod$text
        )
        if (length(node_idx) == 1) {
          annotations$text[[node_idx]] <- mod$nodelabel
          annotations$math_expression[[node_idx]] <- mod$math_expression
        }
      }
    }
  }


  if (length(edges_from) == 0 || length(edges_to) == 0) {
    stop("No edges found in the model. Check the Lavaan syntax.")
  }

  # Create lines dataframe
  lines_df_pre <- data.frame(
    x_start = edges_df$x_start,
    y_start = edges_df$y_start,
    x_end = edges_df$x_end,
    y_end = edges_df$y_end,
    ctrl_x = edges_df$ctrl_x,
    ctrl_y = edges_df$ctrl_y,
    ctrl_x2 = edges_df$ctrl_x2,
    ctrl_y2 = edges_df$ctrl_y2,
    curvature_magnitude = edges_df$curvature,
    rotate_curvature = NA,
    curvature_asymmetry = NA,
    type = ifelse(edges_df$is_directed, "Straight Arrow","Straight Line"),
    color = if (apply_global_edges) edge_color else edges_df$stroke_color,
    end_color = NA,
    color_type = "Single",
    gradient_position = NA,
    width = if (apply_global_edges) line_width else edges_df$stroke_width,
    alpha = if (apply_global_edges) line_alpha else edges_df$alpha,
    arrow = ifelse(edges_df$is_directed, TRUE, NA),
    arrow_type = arrow_type,
    arrow_size = arrow_size,
    two_way = ifelse(edges_df$arrow_config == "bidirectional", TRUE, FALSE),
    lavaan = TRUE,
    network = FALSE,
    line_style = "solid",
    locked = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  if (highlight_free_path) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      ff_params_edge,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color")
      ),
      mode = 'edge'
    )
  }

  # Highlight significant paths if requested
  edge_sig_idx <- which(edges_df$sig == TRUE)
  non_edge_sig_idx <- which(edges_df$sig == FALSE)
  if (highlight_sig_path) {
    lines_df_pre$color[edge_sig_idx] <- sig_path_color
    lines_df_pre$color[non_edge_sig_idx] <- non_sig_path_color
  }

  # Apply edge modifications
  if (modify_params_edge) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      modified_edges,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "width", "alpha", "line_style", "end_color", "gradient_position", "color_type")
      ),
      mode = 'edge'
    )
  }

  # Adjust edge coordinates
  edge_list <- cbind(match(edges_from, node_names), match(edges_to, node_names))
  lines_df <- adjust_edge_coordinates(
    lines_df = lines_df_pre,
    edge_list = edge_list,
    points_df = points_df,
    auto_endpoint_spacing = if (apply_global_edges) line_endpoint_spacing else 1.0
  )

  if ("two_way" %in% colnames(lines_df) && any(lines_df$two_way, na.rm = TRUE)) {
    lines_df[lines_df$two_way, c("x_start", "x_end", "y_start", "y_end")] <-
      lines_df[lines_df$two_way, c("x_start", "x_end", "y_start", "y_end")] +
      c(lavaan_curved_x_shift, lavaan_curved_x_shift, lavaan_curved_y_shift, lavaan_curved_y_shift)
  }


  if (any(lines_df$two_way)) {
    two_way_indices <- which(lines_df$two_way)
    control_points <- mapply(
      calculate_control_point,
      x_start = lines_df$x_start[two_way_indices],
      y_start = lines_df$y_start[two_way_indices],
      x_end = lines_df$x_end[two_way_indices],
      y_end = lines_df$y_end[two_way_indices],
      curvature_magnitude = lavaan_curvature_magnitude,
      rotate_curvature = lavaan_rotate_curvature,
      curvature_asymmetry = lavaan_curvature_asymmetry,
      center_x = mean(node_coords$x),
      center_y = mean(node_coords$y),
      SIMPLIFY = FALSE
    )
    lines_df$ctrl_x[two_way_indices] <- sapply(control_points, `[[`, "ctrl_x")
    lines_df$ctrl_y[two_way_indices] <- sapply(control_points, `[[`, "ctrl_y")
    lines_df$ctrl_x2[two_way_indices] <- sapply(control_points, `[[`, "ctrl_x2")
    lines_df$ctrl_y2[two_way_indices] <- sapply(control_points, `[[`, "ctrl_y2")

    lines_df$curvature_magnitude[two_way_indices] <- lavaan_curvature_magnitude
    lines_df$rotate_curvature[two_way_indices] <- lavaan_rotate_curvature
    lines_df$curvature_asymmetry[two_way_indices] <- lavaan_curvature_asymmetry
  }

  # Apply covariance edge modifications

  if (modify_params_cov_edge) {
    lines_df <- apply_modifications(
      lines_df,
      modified_cov_edges,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = NULL,
        special_case = function(data, idx, mod) {
          # Add input validation
          if (length(idx) == 1 &&
              all(c("x_start", "y_start", "x_end", "y_end") %in% names(data)) &&
              all(c("curvature_magnitude", "rotate_curvature", "curvature_asymmetry", "x_shift", "y_shift") %in% names(mod))) {

            data$x_start[idx] <- data$x_start[idx] + mod$x_shift
            data$x_end[idx] <- data$x_end[idx] + mod$x_shift
            data$y_start[idx] <- data$y_start[idx] + mod$y_shift
            data$y_end[idx] <- data$y_end[idx] + mod$y_shift

            cp <- calculate_control_point(
              x_start = data$x_start[idx],
              y_start = data$y_start[idx],
              x_end = data$x_end[idx],
              y_end = data$y_end[idx],
              curvature_magnitude = mod$curvature_magnitude,
              rotate_curvature = mod$rotate_curvature,
              curvature_asymmetry = mod$curvature_asymmetry,
              center_x = mean(node_coords$x),
              center_y = mean(node_coords$y)
            )

            # Safely assign control points
            if (all(c("ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2", "locked") %in% names(data))) {
              data$ctrl_x[idx] <- cp$ctrl_x
              data$ctrl_y[idx] <- cp$ctrl_y
              data$ctrl_x2[idx] <- cp$ctrl_x2
              data$ctrl_y2[idx] <- cp$ctrl_y2
              data$curvature_magnitude[idx] <- mod$curvature_magnitude
              data$rotate_curvature[idx] <- mod$rotate_curvature
              data$curvature_asymmetry[idx] <- mod$curvature_asymmetry
              data$locked[idx] <- FALSE
            }
          }
          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  lines_df$type[lines_df$curvature_magnitude == 0] <- "Straight Arrow"
  lines_df$type[lines_df$curvature_magnitude != 0] <- "Curved Arrow"


  if (modify_params_edge_xy) {
    lines_df <- apply_modifications(
      lines_df,
      modified_edges_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x_start[idx] <- mod$start_x_shift # data$x_start[idx] + mod$start_x_shift
          data$y_start[idx] <- mod$start_y_shift # data$y_start[idx] + mod$start_y_shift
          data$x_end[idx] <- mod$end_x_shift # data$x_end[idx] + mod$end_x_shift
          data$y_end[idx] <- mod$end_y_shift # data$y_end[idx] + mod$end_y_shift

          if (data$type[idx] %in% c('Curved Line', 'Curved Arrow')) {
            cp <- calculate_control_point(
              x_start = data$x_start[idx],
              y_start = data$y_start[idx],
              x_end = data$x_end[idx],
              y_end = data$y_end[idx],
              curvature_magnitude = data$curvature_magnitude[idx],
              rotate_curvature = data$rotate_curvature[idx],
              curvature_asymmetry = data$curvature_asymmetry[idx],
              center_x = mean(node_coords$x),
              center_y = mean(node_coords$y)
            )

            data$ctrl_x[idx] <- cp$ctrl_x
            data$ctrl_y[idx] <- cp$ctrl_y
            data$ctrl_x2[idx] <- cp$ctrl_x2
            data$ctrl_y2[idx] <- cp$ctrl_y2
          }

          return(data)
        }
      ),
      mode = 'edge'
    )
  }


  # Handle arrow location
  if (exists("lavaan_arrow_location") && lavaan_arrow_location == "start") {
    temp <- lines_df[, c("x_start", "y_start")]
    lines_df[, c("x_start", "y_start")] <- lines_df[, c("x_end", "y_end")]
    lines_df[, c("x_end", "y_end")] <- temp
  }

  edge_labels <- edges_df$label
  # Prepare edge labels
  lines_df0 <- cbind(lines_df, from = edges_from, to = edges_to, text = edge_labels)
  edgelabels_xy_df <- data.frame(x = numeric(nrow(lines_df0)), y = numeric(nrow(lines_df0)))

  for (i in seq_len(nrow(lines_df0))) {
    intp_points <- if (lines_df0$type[i] == "Curved Arrow") {
      create_bezier_curve(
        x_start = lines_df0$x_start[i], y_start = lines_df0$y_start[i],
        x_end = lines_df0$x_end[i], y_end = lines_df0$y_end[i],
        ctrl_x = lines_df0$ctrl_x[i], ctrl_y = lines_df0$ctrl_y[i],
        ctrl_x2 = lines_df0$ctrl_x2[i], ctrl_y2 = lines_df0$ctrl_y2[i], n_points = 100
      )
    } else {
      interpolate_points(
        x_start = lines_df0$x_start[i], y_start = lines_df0$y_start[i],
        x_end = lines_df0$x_end[i], y_end = lines_df0$y_end[i], n = 100
      )
    }

    mid_idx <- ifelse(lines_df0$type[i] == "Curved Arrow",
                      find_peak_point(
                        intp_points,
                        x_start = lines_df0$x_start[i],
                        y_start = lines_df0$y_start[i],
                        x_end = lines_df0$x_end[i],
                        y_end = lines_df0$y_end[i]
                      ),
                      50)
    #mid_idx <- 50
    edgelabels_xy_df[i, ] <- intp_points[mid_idx, c("x", "y")]
  }

  label_coords <- data.frame(
    text = edges_df$label,
    x = edgelabels_xy_df$x,
    y = edgelabels_xy_df$y,
    font = text_font_edges,
    size = if (apply_global_annotations) text_size_edges else edges_df$label_size,
    color = if (apply_global_annotations) text_color_edges else edges_df$label_color,
    fill = if (apply_global_annotations) text_color_fill else ifelse(edges_df$label == "", NA, text_color_fill),
    angle = 0,
    alpha = if (apply_global_annotations) text_alpha_edges else edges_df$label_alpha,
    fontface = text_fontface_edges,
    math_expression = FALSE,
    hjust = 0.5,
    vjust = 0.5,
    lavaan = TRUE,
    network = FALSE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  ) |>
    filter(nzchar(trimws(text)))

  # Apply edge label modifications
  if (modify_params_edgelabel) {
    label_coords <- apply_modifications(
      label_coords,
      modified_edgelabels,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fill", "size", "alpha", "angle", "font", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (highlight_free_path) {
    label_coords <- apply_modifications(
      label_coords,
      ff_params_edgelabel,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (highlight_sig_path) {
    label_coords$fontface[edge_sig_idx] <- sig_label_fontface
    label_coords$fontface[non_edge_sig_idx] <- non_sig_label_fontface
    label_coords$color[edge_sig_idx] <- sig_path_color
    label_coords$color[non_edge_sig_idx] <- non_sig_path_color
  }


  if (modify_params_edgelabel_xy) {
    label_coords <- apply_modifications(
      label_coords,
      modified_edgelabels_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'edge'
    )
  }


  if (modify_params_edgelabel_text) {
    if (nrow(modified_edgelabels_text) > 0) {
      for (i in seq_len(nrow(modified_edgelabels_text))) {
        mod <- modified_edgelabels_text[i, ]
        edge_idx <- which(
          edges_from == mod$lhs &
            edges_to == mod$rhs
        )
        if (length(edge_idx) > 0) {
          label_coords$text[[edge_idx]] <- mod$text
          label_coords$math_expression[[edge_idx]] <- mod$math_expression
        }
      }
    }
  }

  if (remove_edgelabels) {
    label_coords <- data.frame(
      text = character(),
      x = numeric(),
      y = numeric(),
      font = character(),
      size = numeric(),
      color = character(),
      fill = character(),
      angle = numeric(),
      alpha = numeric(),
      fontface = character(),
      math_expression = logical(),
      hjust = numeric(),
      vjust = numeric(),
      lavaan = logical(),
      network = logical(),
      locked = logical(),
      group_label = logical(),
      loop_label = logical(),
      group = character(),
      stringsAsFactors = FALSE
    )
  }

  if (!is.null(data_file)) {
    if (data_file) {
      annotations <- rbind(annotations, label_coords)
    }
  }

  points_df[c("x", "y")] <- lapply(points_df[c("x", "y")], round, 5)

  line_cols <- c("x_start", "y_start", "x_end", "y_end",
                 "ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2")
  lines_df[line_cols] <- lapply(lines_df[line_cols], round, 5)

  annotations[c("x", "y")] <- lapply(annotations[c("x", "y")], round, 5)

  list(points = points_df, lines = lines_df, annotations = annotations)
}

get_svg_dimensions <- function(svg) {
  view_box <- xml_attr(svg, "viewBox")
  if (!is.na(view_box)) {
    dims <- as.numeric(strsplit(view_box, " ")[[1]])
    return(list(width = dims[3], height = dims[4]))
  }
  list(width = as.numeric(gsub("pt", "", xml_attr(svg, "width"))),
       height = as.numeric(gsub("pt", "", xml_attr(svg, "height"))))
}

extract_node_properties <- function(node, svg) {
  dims <- get_svg_dimensions(svg)
  svg_height <- dims$height

  id <- xml_attr(node, "id")
  node_name <- xml_text(xml_find_first(node, "./title"))  # Gets "A", "B", etc.

  text_node <- xml_find_first(node, "./text")
  label <- xml_text(text_node)  # Gets "Start", "Data Collection", etc.

  shape <- ifelse(!is.na(xml_find_first(node, "./polygon")), "rectangle", "oval")

  if (shape == "rectangle") {
    points <- xml_attr(xml_find_first(node, "./polygon"), "points")
    coords <- str_split(points, " ")[[1]][1]  # First point (top-left corner)
    xy <- as.numeric(str_split(coords, ",")[[1]])
    all_coords <- str_split(str_split(points, " ")[[1]], ",")
    all_coords_matrix <- matrix(as.numeric(unlist(all_coords)), ncol = 2, byrow = TRUE)
    width <- abs(xy[1] - all_coords_matrix[2, 1])
    height <- abs(xy[2] - all_coords_matrix[4, 2])

  } else {
    rx <- as.numeric(xml_attr(xml_find_first(node, "./ellipse"), "rx"))
    ry <- as.numeric(xml_attr(xml_find_first(node, "./ellipse"), "ry"))
    width <- 2 * rx
    height <- 2 * ry
  }

  text_x <- as.numeric(xml_attr(text_node, "x"))
  text_y <- as.numeric(xml_attr(text_node, "y"))
  font_size <- as.numeric(xml_attr(text_node, "font-size"))
  text_color <- xml_attr(text_node, "fill")

  #center_y <- svg_height - center_y
  text_y <- svg_height - text_y

  stroke <- ifelse(shape == "rectangle",
                   xml_attr(xml_find_first(node, "./polygon"), "stroke"),
                   xml_attr(xml_find_first(node, "./ellipse"), "stroke"))

  fill <- ifelse(shape == "rectangle",
                 xml_attr(xml_find_first(node, "./polygon"), "fill"),
                 xml_attr(xml_find_first(node, "./ellipse"), "fill"))

  fill_opacity <- ifelse(shape == "rectangle",
                         xml_attr(xml_find_first(node, "./polygon"), "fill-opacity"),
                         xml_attr(xml_find_first(node, "./ellipse"), "fill-opacity"))

  if (is.na(fill_opacity)) {
    # If no explicit fill-opacity, check if fill color has alpha in hex format
    if (grepl("^#([A-Fa-f0-9]{8})$", fill)) {
      # Extract alpha from 8-digit hex (last 2 digits)
      alpha_hex <- substr(fill, 8, 9)
      fill_opacity <- as.numeric(strtoi(alpha_hex, 16)) / 255
    } else {
      fill_opacity <- 1.0 # Default to fully opaque
    }
  } else {
    fill_opacity <- as.numeric(fill_opacity)
  }

  data.frame(
    id = id,
    node_name = node_name,
    label = label,
    shape = shape,
    x = text_x,
    y = text_y,
    width = width,
    height = height,
    stroke_color = stroke,
    fill_color = ifelse(fill == 'none', '#FFFFFF' , fill),
    alpha = fill_opacity,
    text_x = text_x,
    text_y = text_y,
    font_size = font_size * 1.4,
    text_color = text_color,
    stringsAsFactors = FALSE
  )
}
extract_edge_properties <- function(edge, svg) {
  dims <- get_svg_dimensions(svg)
  svg_height <- dims$height

  id <- xml_attr(edge, "id")
  title <- xml_text(xml_find_first(edge, "./title"))

  edge_nodes <- str_match(title, "(\\w+)\\s*->\\s*(\\w+)")[1, 2:3]
  is_directed <- !any(is.na(edge_nodes))

  if (any(is.na(edge_nodes))) {
    edge_nodes <- str_match(title, "(\\w+)\\s*~~\\s*(\\w+)")[1, 2:3]
    is_directed <- FALSE
  }

  # Path properties
  path <- xml_find_first(edge, "./path")
  path_d <- xml_attr(path, "d")
  stroke <- xml_attr(path, "stroke")
  `%||%` <- function(a, b) ifelse(!is.na(a) & !is.null(a), a, b)
  stroke_width <- as.numeric(xml_attr(path, "stroke-width") %||% 1)

  is_curved <- FALSE
  ctrl_x <- NA
  ctrl_y <- NA
  ctrl_x2 <- NA
  ctrl_y2 <- NA
  curvature <- 0

  polygons <- xml_find_all(edge, "./polygon")
  arrowheads <- list()

  if (length(polygons) > 0) {
    # Extract arrowhead positions by analyzing their coordinates
    for (polygon in polygons) {
      points <- xml_attr(polygon, "points")
      if (!is.na(points)) {
        coords <- str_extract_all(points, "[0-9.-]+")[[1]] |> as.numeric()
        if (length(coords) >= 6) {  # Need at least 3 points (x,y pairs) for a triangle
          # Calculate centroid of the arrowhead
          centroid_x <- mean(coords[seq(1, length(coords), by = 2)])
          centroid_y <- mean(coords[seq(2, length(coords), by = 2)])

          arrowheads <- c(arrowheads, list(list(
            x = centroid_x,
            y = centroid_y,
            fill = xml_attr(polygon, "fill"),
            stroke = xml_attr(polygon, "stroke")
          )))
        }
      }
    }
  }

  arrowhead_count <- length(arrowheads)
  arrow_config <- "none"

  if (arrowhead_count > 0) {
    # Get path endpoints to determine which end is which
    path_coords <- if (!is.na(path_d)) str_extract_all(path_d, "[0-9.-]+")[[1]] |> as.numeric() else numeric()
    if (length(path_coords) >= 4) {
      start_x <- path_coords[1]
      start_y <- svg_height - path_coords[2]
      end_x <- path_coords[length(path_coords)-1]
      end_y <- svg_height - path_coords[length(path_coords)]

      # Calculate distances to determine which arrowhead is at which end
      if (arrowhead_count == 1) {
        # Single arrowhead - determine which end it's on
        arrow_x <- arrowheads[[1]]$x
        arrow_y <- arrowheads[[1]]$y

        dist_to_start <- sqrt((arrow_x - start_x)^2 + (arrow_y - start_y)^2)
        dist_to_end <- sqrt((arrow_x - end_x)^2 + (arrow_y - end_y)^2)

        arrow_config <- ifelse(dist_to_start < dist_to_end, "forward", "backward")

      } else if (arrowhead_count == 2) {
        # Two arrowheads - bidirectional edge
        arrow_config <- "bidirectional"
      } else if (arrowhead_count > 2) {
        # Multiple arrowheads (unusual, but handle it)
        arrow_config <- "multiple"
      }
    }
  }

  # Arrowhead properties
  polygon <- xml_find_first(edge, "./polygon")
  has_arrowhead <- !is.na(polygon)
  arrow_fill <- ifelse(!is.na(polygon), xml_attr(polygon, "fill"), NA)
  arrow_stroke <- ifelse(!is.na(polygon), xml_attr(polygon, "stroke"), NA)

  is_directed <- is_directed || has_arrowhead

  arrow_fill_opacity <- ifelse(!is.na(polygon), xml_attr(polygon, "fill-opacity"), NA)

  # Handle arrowhead opacity fallbacks
  if (!is.na(arrow_fill) && is.na(arrow_fill_opacity)) {
    if (grepl("^#([A-Fa-f0-9]{8})$", arrow_fill)) {
      alpha_hex <- substr(arrow_fill, 8, 9)
      arrow_fill_opacity <- as.numeric(strtoi(alpha_hex, 16)) / 255
    } else if (grepl("^rgba\\(", arrow_fill)) {
      rgba_match <- str_match(arrow_fill, "rgba\\((\\d+),\\s*(\\d+),\\s*(\\d+),\\s*([0-9.]+)\\)")
      if (!is.na(rgba_match[1, 1])) {
        arrow_fill_opacity <- as.numeric(rgba_match[1, 5])
      }
    } else {
      arrow_fill_opacity <- 1.0
    }
  }

  text_nodes <- xml_find_all(edge, ".//text")
  label_alpha <- 1
  if (length(text_nodes) > 0) {
    label <- xml_text(text_nodes[1])
    label_x <- as.numeric(xml_attr(text_nodes[1], "x") %||% NA)
    label_y <- as.numeric(xml_attr(text_nodes[1], "y") %||% NA)
    label_color <- xml_attr(text_nodes[1], "fill") %||% NA
    label_size <- as.numeric(xml_attr(text_nodes[1], "font-size") %||% NA)

    label_y <- ifelse(!is.na(label_y), svg_height - label_y, NA)


  } else {
    label <- label_x <- label_y <- label_color <- label_size <- NA
  }

  path_coords <- str_extract_all(path_d, "[0-9.-]+")[[1]] |> as.numeric()
  if (length(path_coords) >= 2) {
    start_x <- path_coords[1]
    start_y <- svg_height - path_coords[2]
    end_x <- path_coords[length(path_coords)-1]
    end_y <- svg_height - path_coords[length(path_coords)]
  } else {
    start_x <- start_y <- end_x <- end_y <- NA
  }


  if (is_curved) {
    curvature <- calculate_curvature_magnitude(
      start_x, start_y, end_x, end_y,
      ctrl_x, ctrl_y, ctrl_x2, ctrl_y2
    )
  }

  data.frame(
    source = edge_nodes[1],
    target = edge_nodes[2],
    label = label,
    label_x = label_x,
    label_y = label_y,
    label_color = label_color,
    label_alpha = label_alpha,
    label_size = label_size * 1.4,
    x_start = start_x,
    y_start = start_y,
    x_end = end_x,
    y_end = end_y,
    stroke_color = stroke,
    stroke_width = stroke_width,
    arrow_fill = arrow_fill,
    arrow_config = arrow_config,
    alpha = as.numeric(arrow_fill_opacity),
    arrow_stroke = arrow_stroke,
    is_directed = is_directed,
    is_curved = is_curved,
    ctrl_x = NA,
    ctrl_y = NA,
    ctrl_x2 = NA,
    ctrl_y2 = NA,
    curvature = NA,
    stringsAsFactors = FALSE
  )
}

extract_node_matches <- function(dot_code) {
  pattern <- "([\"']?)([a-zA-Z0-9_]+)\\1\\s*\\[([^\\]]+)\\]"

  matches <- str_match_all(dot_code, pattern)[[1]]

  if (nrow(matches) == 0) {
    return(data.frame(
      node_id = character(),
      attributes = character(),
      stringsAsFactors = FALSE
    ))
  }

  data.frame(
    node_id = matches[, 3],  # The actual node name/number
    attributes = matches[, 4],  # The attributes inside []
    stringsAsFactors = FALSE
  )
}

scale_coordinates_centered <- function(nodes_df, edges_df = NULL, new_min = -2, new_max = 2) {
  # Calculate scaling parameters from nodes_df
  x_center <- (min(nodes_df$x, na.rm = TRUE) + max(nodes_df$x, na.rm = TRUE)) / 2
  y_center <- (min(nodes_df$y, na.rm = TRUE) + max(nodes_df$y, na.rm = TRUE)) / 2
  x_range <- max(nodes_df$x, na.rm = TRUE) - min(nodes_df$x, na.rm = TRUE)
  y_range <- max(nodes_df$y, na.rm = TRUE) - min(nodes_df$y, na.rm = TRUE)

  max_range <- max(x_range, y_range)
  scale_factor <- (new_max - new_min) / max_range

  nodes_df_scaled <- nodes_df |>
    mutate(
      x = (x - x_center) * scale_factor,
      y = (y - y_center) * scale_factor,
      text_x = (text_x - x_center) * scale_factor,
      text_y = (text_y - y_center) * scale_factor
    )

  if (!is.null(edges_df)) {
    edges_df_scaled <- edges_df |>
      mutate(
        x_start = (x_start - x_center) * scale_factor,
        y_start = (y_start - y_center) * scale_factor,
        x_end= (x_end - x_center) * scale_factor,
        y_end = (y_end - y_center) * scale_factor,
        label_x = (label_x - x_center) * scale_factor,
        label_y = (label_y - y_center) * scale_factor
      )

    return(list(nodes = nodes_df_scaled, edges = edges_df_scaled))
  }

  return(list(nodes = nodes_df_scaled, edges = NULL))
}

generate_network_layout <- function(network_object,
                                    edges,
                                    nodes,
                                    layout_method = "fr",
                                    directed = FALSE,
                                    dim_reduction_method = NULL,
                                    random_seed = NULL,
                                    flip_layout = FALSE,
                                    flip_direction = NULL,
                                    rotate_layout = FALSE,
                                    rotate_angle = 0) {


  if (!is.null(random_seed)) {
    set.seed(random_seed)  # Set the seed if provided
  }

  if (layout_method == 'dim_reduction') {
    use_dim_reduction <- TRUE
  } else use_dim_reduction <- FALSE

  if (!is.null(network_object)) {
    if (inherits(network_object, c("igraph"))) {
      is_bipartite <- igraph::is_bipartite(network_object)
    } else if (inherits(network_object, c("network"))) {
      is_bipartite <- network::is.bipartite(network_object)
    } else {
      is_bipartite <- FALSE
    }
  } else {
    is_bipartite <- FALSE
  }

  if (!is.null(network_object)) {
    if (inherits(network_object, c("igraph"))) {
      graph <- network_object
    } else if (inherits(network_object, c("network"))) {

      network_object %v% "type" <- seq_len(network.size(network_object)) > network_object %n% "bipartite"

      vertex_types <- network::get.vertex.attribute(network_object, "type")  # Must exist for bipartite networks

      if (is_bipartite) {
        graph <- graph_from_data_frame(
          d = edges,
          directed = network::is.directed(network_object),
          vertices = data.frame(
            name = nodes,
            type = vertex_types
          )
        )
      } else {
        graph <- graph_from_data_frame(
          d = edges,
          directed = network::is.directed(network_object),
          vertices = data.frame(
            name = nodes
          )
        )
      }
    }
  } else {
    graph <- graph_from_data_frame(d = edges, vertices = nodes, directed = directed)
  }

  if (is_bipartite == TRUE) {
    # Use bipartite layout if graph is bipartite
    layout <- igraph::layout_as_bipartite(graph) |>
      as.data.frame() |>
      dplyr::rename(x = V1, y = V2) |>
      dplyr::mutate(node = as.character(nodes$node))

    # Flip y-coordinates to make layout more intuitive
    layout$y <- -layout$y

  } else if (use_dim_reduction) {
    adjacency_matrix <- as.matrix(as_adjacency_matrix(graph))
    num_nodes <- nrow(adjacency_matrix)

    if (num_nodes < 3) {
      showNotification(
        "Not enough nodes for dimensionality reduction. Falling back to Fruchterman-Reingold layout.",
        type = "warning",
        duration = 5
      )
      layout <- layout_with_fr(graph) |>
        as.data.frame() |>
        rename(x = V1, y = V2) |>
        mutate(node = as.character(nodes$node))
    } else {
      layout <- tryCatch({
        if (dim_reduction_method == "tsne") {
          tsne_perplexity <- max(5, min(30, num_nodes - 1))
          Rtsne::Rtsne(adjacency_matrix, perplexity = tsne_perplexity, verbose = FALSE)$Y |>
            as.data.frame() |>
            rename(x = V1, y = V2) |>
            mutate(node = as.character(nodes$node))
        } else if (dim_reduction_method == "umap") {
          umap_neighbors <- max(2, min(15, num_nodes - 1))  # Dynamically set neighbors
          umap::umap(adjacency_matrix, n_neighbors = umap_neighbors)$layout |>
            as.data.frame() |>
            rename(x = V1, y = V2) |>
            mutate(node = as.character(nodes$node))

        } else if (dim_reduction_method == "pca") {
          prcomp(adjacency_matrix, center = TRUE, scale. = TRUE)$x[, 1:2] |>
            as.data.frame() |>
            rename(x = PC1, y = PC2) |>
            mutate(node = as.character(nodes$node))
        } else {
          stop("Invalid dimensionality reduction method selected.")
        }
      }, error = function(e) {
        showNotification(
          paste("Dimensionality reduction failed:", e$message, "Falling back to Fruchterman-Reingold layout."),
          type = "warning",
          duration = 5
        )
        layout_with_fr(graph) |>  # Fallback to Fruchterman-Reingold
          as.data.frame() |>
          rename(x = V1, y = V2) |>
          mutate(node = as.character(nodes$node))
      })
    }
  } else { # other layout methods
    layout <- switch(layout_method,
                     "fr" = layout_with_fr(graph), # Fruchterman-Reingold
                     "kk" = layout_with_kk(graph), # Kamada-Kawai
                     "circle" = layout_in_circle(graph), # Circular layout
                     "grid" = layout_on_grid(graph), # Grid layout
                     "random" = layout_randomly(graph), # Random layout
                     stop("Invalid layout method specified!")
    ) |>
      as.data.frame() |>
      dplyr::rename(x = V1, y = V2) |>
      dplyr::mutate(node = as.character(nodes$node))
  }

  if (flip_layout) {
    flipped <- flip_around_center(layout, flip_direction)
    layout$x <- flipped$x
    layout$y <- flipped$y
  }

  if (rotate_layout) {
    rotated <- rotate_around_center(layout, rotate_angle)
    layout$x <- rotated$x
    layout$y <- rotated$y
  }

  return(list(graph = graph,
              layout = layout,
              is_bipartite = is_bipartite))
}



generate_graph_from_network <- function(graph,
                                        layout,
                                        edges,
                                        nodes,
                                        is_bipartite,
                                        directed = TRUE,
                                        layout_width = 30,
                                        layout_height = 30, x_center = 0, y_center = 0,
                                        node_shape = "circle",
                                        node_size = 10,
                                        node_alpha = 1,
                                        node_fill_color = "#1262b3",
                                        node_border_color = "#0f993d", node_border_width = 1,
                                        node_width_height_ratio = 1,
                                        line_width = 1, line_color = "#000000",
                                        line_alpha = 1,
                                        min_edge_width = 0.5, max_edge_width = 3, scale_by_weight = FALSE,
                                        line_endpoint_spacing = 0,
                                        arrow_type = "closed",
                                        arrow_size = 0.1,
                                        node_label_font = "sans", node_label_size = 10,
                                        node_label_color = "#000000",
                                        node_label_alpha = 1, node_label_fontface = "plain",
                                        edge_label_font = "sans", edge_label_size = 10,
                                        edge_label_color = "#000000",
                                        edge_label_fill = "#FFFFFF",
                                        edge_label_alpha = 1, edge_label_fontface = "plain",
                                        zoom_factor = 1.2,
                                        annotate_nodes = TRUE,
                                        annotate_edges = TRUE,
                                        remove_edgelabels = FALSE,
                                        # random_seed = NULL,
                                        use_clustering = FALSE,
                                        clustering_method = "louvain",
                                        cluster_palette = 'rainbow',
                                        bezier_network_edges = FALSE,
                                        network_edges_curvature_magnitude = 0.5,
                                        network_edges_rotate_curvature = FALSE,
                                        network_edges_curvature_asymmetry = 0,
                                        modify_params_edge = FALSE,
                                        modified_edges = NULL,
                                        modify_params_edgelabel = FALSE,
                                        modified_edgelabels = NULL,
                                        modify_params_edgelabel_xy = FALSE,
                                        modified_edgelabels_xy = NULL,
                                        modify_params_node = FALSE,
                                        modified_nodes = NULL,
                                        modify_params_node_xy = FALSE,
                                        modified_nodes_xy = NULL,
                                        modify_params_nodelabel = FALSE,
                                        modified_nodelabels = NULL,
                                        modify_params_nodelabel_xy = FALSE,
                                        modified_nodelabels_xy = NULL,
                                        modify_params_nodelabel_text = FALSE,
                                        modified_nodelabels_text = NULL,
                                        modify_params_bezier_edge = FALSE,
                                        modified_bezier_edges = NULL,
                                        modify_params_edge_xy = FALSE,
                                        modified_edges_xy = NULL,
                                        modify_params_edgelabel_text = FALSE,
                                        modified_edgelabels_text = NULL,
                                        change_bipartite_group = FALSE,
                                        apply_bipartite_nodes = FALSE,
                                        apply_bipartite_edges = FALSE,
                                        apply_bipartite_annotations = FALSE,
                                        last_state = NULL,
                                        which_group = "1") {

  apply_modifications <- function(data, modifications, config, mode) {
    if (is.null(modifications) || nrow(modifications) == 0) return(data)
    modified_data <- data

    for (i in seq_len(nrow(modifications))) {
      mod <- modifications[i, ]

      if (mode == 'edge') {
        idx <- which(
          edges$source == mod$lhs &
            edges$target == mod$rhs
        )
      } else if (mode == 'node') {
        idx <- which(
          layout$node == mod$text
        )
      }

      if (length(idx) == 1) {
        for (col in config$modify_cols) {
          if (col %in% names(mod) && col %in% names(modified_data)) {
            modified_data[idx, col] <- mod[[col]]
          }
        }

        if (!is.null(config$special_case)) {
          modified_data <- config$special_case(modified_data, idx, mod)
        }
      }
    }
    return(modified_data)
  }

  edges$source <- as.character(edges$source)
  edges$target <- as.character(edges$target)


  edge_list <- as.data.frame(edges[, c("source", "target")])


  if (use_clustering) {
    num_clusters <- NULL

    edge_weights <- E(graph)$weight

    if (!is.null(edge_weights) && any(edge_weights < 0, na.rm = TRUE)) {
      n_negative <- sum(edge_weights < 0, na.rm = TRUE)

      showNotification(
        paste("Warning: Network contains", n_negative,
              "negative edge weights. Taking absolute values for clustering."),
        type = "warning",
        duration = 5
      )

      E(graph)$weight <- abs(edge_weights)
    }

    if (!is.null(edge_weights) && any(edge_weights == 0, na.rm = TRUE)) {
      # Add small epsilon to avoid division by zero issues
      zero_weights <- which(edge_weights == 0)
      E(graph)$weight[zero_weights] <- 0.0001
    }

    if (!is_connected(graph)) {
      showNotification(
        "Network is not fully connected. Clustering results may be fragmented.",
        type = "warning",
        duration = 5
      )
    }

    communities <- tryCatch({
      switch(
        clustering_method,
        "louvain" = cluster_louvain(graph, weights = E(graph)$weight),
        "leiden" = cluster_leiden(graph, weights = E(graph)$weight),
        "walktrap" = cluster_walktrap(graph, weights = E(graph)$weight),
        "fast_greedy" = cluster_fast_greedy(graph, weights = E(graph)$weight)
      )
    }, error = function(e) {
      showNotification(
        paste("Clustering failed:", e$message, "Using fallback clustering."),
        type = "warning",
        duration = 5
      )

      tryCatch({
        cluster_spinglass(graph, weights = if(!is.null(E(graph)$weight)) abs(E(graph)$weight))
      }, error = function(e2) {
        tryCatch({
          cluster_edge_betweenness(graph, weights = if(!is.null(E(graph)$weight)) abs(E(graph)$weight))
        }, error = function(e3) {
          components(graph)$membership
        })
      })
    })

    if (!is.null(communities)) {
      if (inherits(communities, "communities")) {
        nodes$community <- membership(communities)
        num_clusters <- max(nodes$community, na.rm = TRUE)
      } else if (is.numeric(communities) || is.integer(communities)) {
        nodes$community <- communities
        num_clusters <- max(communities, na.rm = TRUE)
      } else {
        nodes$community <- rep(1, vcount(graph))
        num_clusters <- 1
      }
    } else {
      nodes$community <- rep(1, vcount(graph)) # Default to one cluster if clustering fails
      num_clusters <- 1
    }

    if (is.na(num_clusters) || num_clusters <= 0) {
      num_clusters <- 1
      nodes$community <- rep(1, length(nodes$community))
    }

    if (!is.integer(nodes$community)) {
      nodes$community <- as.integer(nodes$community)
    }

    palette_max_colors <- list(
      rainbow = Inf,  # Unlimited
      Set3 = 12,
      Paired = 12,
      Dark2 = 8,
      Accent = 8,
      Pastel1 = 9,
      Pastel2 = 8,
      Spectral = 11,
      YlGnBu = 9,
      RdYlBu = 11,
      smplot2 = 20
    )

    palette_function <- switch(
      cluster_palette,
      "rainbow" = function(n) rainbow(n),
      "Set3" = function(n) RColorBrewer::brewer.pal(min(n, 12), "Set3"),
      "Paired" = function(n) RColorBrewer::brewer.pal(min(n, 12), "Paired"),
      "Dark2" = function(n) RColorBrewer::brewer.pal(min(n, 8), "Dark2"),
      "Accent" = function(n) RColorBrewer::brewer.pal(min(n, 8), "Accent"),
      "Pastel1" = function(n) RColorBrewer::brewer.pal(min(n, 9), "Pastel1"),
      "Pastel2" = function(n) RColorBrewer::brewer.pal(min(n, 8), "Pastel2"),
      "Spectral" = function(n) RColorBrewer::brewer.pal(min(n, 11), "Spectral"),
      "YlGnBu" = function(n) RColorBrewer::brewer.pal(min(n, 9), "YlGnBu"),
      "RdYlBu" = function(n) RColorBrewer::brewer.pal(min(n, 11), "RdYlBu"),
      "smplot2" = function(n) head(smplot2::sm_palette(), n)
    )

    max_colors <- palette_max_colors[[cluster_palette]]

    if (cluster_palette != "rainbow" && num_clusters > max_colors) {
      showNotification(
        paste(cluster_palette, "supports a maximum of", max_colors, "colors. Falling back to Rainbow palette."),
        type = "warning",
        duration = 5
      )
      palette_function <- rainbow
    }

    valid_communities <- nodes$community
    if (any(is.na(valid_communities))) {
      valid_communities[is.na(valid_communities)] <- 1
    }
    if (any(valid_communities <= 0)) {
      valid_communities[valid_communities <= 0] <- 1
    }
    if (any(valid_communities > num_clusters)) {
      valid_communities[valid_communities > num_clusters] <- num_clusters
    }

    if (num_clusters > 0 && all(valid_communities >= 1 & valid_communities <= num_clusters)) {
      node_colors <- palette_function(num_clusters)[valid_communities]
    } else {
      node_colors <- rep(node_fill_color, vcount(graph))
      showNotification(
        "Community assignment invalid. Using default node colors.",
        type = "warning",
        duration = 3
      )
    }

  } else {
    node_colors <- node_fill_color
  }

  layout_min_x <- min(layout$x)
  layout_min_y <- min(layout$y)
  layout_max_x <- max(layout$x)
  layout_max_y <- max(layout$y)

  layout <- layout |>
    mutate(
      x = (x - layout_min_x) / (layout_max_x - layout_min_x) * layout_width + x_center - layout_width / 2,
      y = (y - layout_min_y) / (layout_max_y - layout_min_y) * layout_height + y_center - layout_height / 2
    )



  # Apply node position modifications
  if (modify_params_node_xy) {
    layout <- apply_modifications(
      layout,
      modified_nodes_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0), # No direct column mods
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )

  }

  if (is_bipartite) {
    group_selector <- if (apply_bipartite_nodes) !vertex_attr(graph)$type else vertex_attr(graph)$type
  } else {
    group_selector <- NULL
  }


  points <- layout |>
    dplyr::left_join(nodes, by = c("node" = "node")) |>
    mutate(
      shape = node_shape,
      color = node_colors,
      size =  node_size,
      border_color = node_border_color,
      border_width = node_border_width,
      alpha = node_alpha,
      width_height_ratio = node_width_height_ratio,
      orientation = 0,
      lavaan = FALSE,
      network = TRUE,
      locked = FALSE,
      group = which_group
    ) |>
    select(
      x, y, shape, color, size, border_color, border_width, alpha, width_height_ratio, orientation,
      lavaan, network, locked, group
    )

  if (!is.null(group_selector)) {
    network_points <- which(last_state$points$network == TRUE)
    network_points <- network_points[group_selector]
    last_state$points[, c('x', 'y')] <- points[, c('x', 'y')]
    last_state$points[network_points, ] <- points[group_selector,]
    points <- last_state$points
  }

  if (modify_params_node) {
    points <- apply_modifications(
      points,
      modified_nodes,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "alpha", "shape", "size", "border_color", "border_width", "width_height_ratio"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }


  if (inherits(edges$weight, 'character')) {
    scale_by_weight <- FALSE
  }

  if ("weight" %in% colnames(edges) && scale_by_weight) {
    edges <- edges |>
      mutate(
        scaled_width = rescale_values(weight, to = c(min_edge_width, max_edge_width))
      )
  } else {
    edges <- edges |>
      mutate(scaled_width = line_width)
  }

  # Prepare lines data frame

  lines <- edges |>
    dplyr::left_join(layout, by = c("source" = "node")) |>
    dplyr::rename(x_start = x, y_start = y) |>
    dplyr::left_join(layout, by = c("target" = "node")) |>
    dplyr::rename(x_end = x, y_end = y) |>
    dplyr::mutate(
      ctrl_x = NA,
      ctrl_y = NA,
      ctrl_x2 = NA,
      ctrl_y2 = NA,
      curvature_magnitude = NA,
      rotate_curvature = NA,
      curvature_asymmetry = NA,
      type = ifelse(bezier_network_edges == FALSE, ifelse(directed, "Straight Arrow", "Straight Line"), ifelse(directed, "Curved Arrow", "Curved Line")),
      color = line_color,
      end_color = NA,
      color_type = "Single",
      gradient_position = NA,
      width = scaled_width,
      alpha = line_alpha,
      arrow = ifelse(directed, directed, NA),
      arrow_type = ifelse(directed, arrow_type, NA),
      arrow_size = ifelse(directed, arrow_size, NA),
      two_way = ifelse(two_way, TRUE, FALSE),
      lavaan = FALSE,
      network = TRUE,
      line_style = "solid",
      locked = FALSE,
      group = which_group
    ) |>
    dplyr::select(
      x_start, y_start, x_end, y_end, ctrl_x, ctrl_y, ctrl_x2, ctrl_y2,
      curvature_magnitude, rotate_curvature, curvature_asymmetry, type, color, end_color,
      color_type, gradient_position, width, alpha, arrow, arrow_type,
      arrow_size, two_way, lavaan, network, line_style, locked, group
    )

  node_mapping <- setNames(seq_along(nodes$node), nodes$node)
  numeric_edge_list <- matrix(
    c(node_mapping[edges$source], node_mapping[edges$target]),
    ncol = 2
  )

  if (modify_params_edge) {
    if (nrow(modified_edges) > 0) {
      for (i in seq_len(nrow(modified_edges))) {
        mod <- modified_edges[i, ]

        edge_idx <- which(
          edges$source == mod$lhs &
            edges$target == mod$rhs
        )

        if (length(edge_idx) == 1) {
          lines$color[[edge_idx]] <- mod$color
          lines$width[[edge_idx]] <- mod$width
          lines$alpha[[edge_idx]] <- mod$alpha
          lines$line_style[[edge_idx]] <- mod$line_style
          lines$end_color[[edge_idx]] <- mod$end_color
          lines$gradient_position[[edge_idx]] <- mod$gradient_position
          lines$color_type[[edge_idx]] <- mod$color_type
        }
      }
    }
  }

  lines <- adjust_edge_coordinates(
    lines_df = lines,
    edge_list = numeric_edge_list,
    points_df = points,
    auto_endpoint_spacing = line_endpoint_spacing
  )


  if (bezier_network_edges == TRUE) {
    bezier_indices <- which(lines$type %in% c('Curved Arrow', 'Curved Line'))

    control_points <- mapply(
      calculate_control_point,
      x_start = lines$x_start[bezier_indices],
      y_start = lines$y_start[bezier_indices],
      x_end = lines$x_end[bezier_indices],
      y_end = lines$y_end[bezier_indices],
      curvature_magnitude = network_edges_curvature_magnitude,
      rotate_curvature = network_edges_rotate_curvature,
      curvature_asymmetry = network_edges_curvature_asymmetry,
      SIMPLIFY = FALSE
    )

    # Assign the calculated control points to lines
    lines$ctrl_x[bezier_indices] <- sapply(control_points, `[[`, "ctrl_x")
    lines$ctrl_y[bezier_indices] <- sapply(control_points, `[[`, "ctrl_y")
    lines$ctrl_x2[bezier_indices] <- sapply(control_points, `[[`, "ctrl_x2")
    lines$ctrl_y2[bezier_indices] <- sapply(control_points, `[[`, "ctrl_y2")

    lines$curvature_magnitude[bezier_indices] <- network_edges_curvature_magnitude
    lines$rotate_curvature[bezier_indices] <- network_edges_rotate_curvature
    lines$curvature_asymmetry[bezier_indices] <- network_edges_curvature_asymmetry
    lines$locked[bezier_indices] <- FALSE
  }



  if (modify_params_bezier_edge) {
    lines <- apply_modifications(
      lines,
      modified_bezier_edges,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = NULL,
        special_case = function(data, idx, mod) {
          if (length(idx) == 1 &&
              all(c("x_start", "y_start", "x_end", "y_end") %in% names(data)) &&
              all(c("curvature_magnitude", "rotate_curvature", "curvature_asymmetry", "x_shift", "y_shift") %in% names(mod))) {

            data$x_start[idx] <- data$x_start[idx] + mod$x_shift
            data$x_end[idx] <- data$x_end[idx] + mod$x_shift
            data$y_start[idx] <- data$y_start[idx] + mod$y_shift
            data$y_end[idx] <- data$y_end[idx] + mod$y_shift

            cp <- calculate_control_point(
              x_start = data$x_start[idx],
              y_start = data$y_start[idx],
              x_end = data$x_end[idx],
              y_end = data$y_end[idx],
              curvature_magnitude = mod$curvature_magnitude,
              rotate_curvature = mod$rotate_curvature,
              curvature_asymmetry = mod$curvature_asymmetry
            )

            # Safely assign control points
            if (all(c("ctrl_x", "ctrl_y", "locked") %in% names(data))) {
              data$ctrl_x[idx] <- cp$ctrl_x
              data$ctrl_y[idx] <- cp$ctrl_y
              data$ctrl_x2[idx] <- cp$ctrl_x2
              data$ctrl_y2[idx] <- cp$ctrl_y2
              data$curvature_magnitude[idx] <- mod$curvature_magnitude
              data$rotate_curvature[idx] <- mod$rotate_curvature
              data$curvature_asymmetry[idx] <- mod$curvature_asymmetry
              data$locked[idx] <- FALSE
              data$type[idx] <- ifelse (directed, 'Curved Arrow', 'Curved Line')
            }
          }
          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  lines$type[lines$curvature_magnitude == 0] <- "Straight Arrow"
  lines$type[lines$curvature_magnitude != 0] <- "Curved Arrow"

  if (modify_params_edge_xy) {
    lines <- apply_modifications(
      lines,
      modified_edges_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x_start[idx] <- mod$start_x_shift # data$x_start[idx] + mod$start_x_shift
          data$y_start[idx] <- mod$start_y_shift # data$y_start[idx] + mod$start_y_shift
          data$x_end[idx] <- mod$end_x_shift # data$x_end[idx] + mod$end_x_shift
          data$y_end[idx] <- mod$end_y_shift # data$y_end[idx] + mod$end_y_shift

          if (data$type[idx] %in% c('Curved Line', 'Curved Arrow')) {
            cp <- calculate_control_point(
              x_start = data$x_start[idx],
              y_start = data$y_start[idx],
              x_end = data$x_end[idx],
              y_end = data$y_end[idx],
              curvature_magnitude = data$curvature_magnitude[idx],
              rotate_curvature = data$rotate_curvature[idx],
              curvature_asymmetry = data$curvature_asymmetry[idx]
            )

            data$ctrl_x[idx] <- cp$ctrl_x
            data$ctrl_y[idx] <- cp$ctrl_y
            data$ctrl_x2[idx] <- cp$ctrl_x2
            data$ctrl_y2[idx] <- cp$ctrl_y2
          }

          return(data)
        }
      ),
      mode = 'edge'
    )
  }


  edgelabels_xy_df <- data.frame(x = rep(NA_real_, nrow(lines)),
                                 y = rep(NA_real_, nrow(lines)))

  for (i in 1:nrow(lines)) {

    intp_points <-
      if (lines$type[i] == "Curved Arrow" || lines$type[i] == "Curved Line") {
        create_bezier_curve(
          x_start = lines$x_start[i], y_start = lines$y_start[i],
          x_end = lines$x_end[i], y_end = lines$y_end[i],
          ctrl_x = lines$ctrl_x[i], ctrl_y = lines$ctrl_y[i],
          ctrl_x2 = lines$ctrl_x2[i], ctrl_y2 = lines$ctrl_y2[i], n_points = 100
        )
      } else if (lines$type[i] == "Straight Arrow" || lines$type[i] == "Straight Line") {
        interpolate_points(
          x_start = lines$x_start[i], y_start = lines$y_start[i],
          x_end = lines$x_end[i], y_end = lines$y_end[i], n = 100
        )
      }

    mid_index <- 50
    edgelabels_xy_df$x[i] <- intp_points$x[mid_index]
    edgelabels_xy_df$y[i] <- intp_points$y[mid_index]
  }

  edges$weight <- if(is.numeric(edges$weight)) round(edges$weight, 3) else edges$weight

  weight_annotations <- if (remove_edgelabels == FALSE) {
    if (!all(is.na(edges$weight))) {
      lines |>
        mutate(weight = edges$weight) |>
        mutate(
          text = as.character(edges$weight),
          x = edgelabels_xy_df$x,
          y = edgelabels_xy_df$y,
          font = edge_label_font,
          size = edge_label_size,
          color = edge_label_color,
          fill = ifelse(as.character(edges$weight) == "", NA, edge_label_fill),
          angle = 0,
          alpha = edge_label_alpha,
          fontface = edge_label_fontface,
          math_expression = FALSE,
          hjust = 0.5,
          vjust = 0.5,
          lavaan = FALSE,
          network = TRUE,
          locked = FALSE,
          group_label = FALSE,
          loop_label = FALSE,
          group = which_group,
        ) |>
        select(text, x, y, font, size, color, fill, angle, alpha, fontface, math_expression, hjust, vjust, lavaan, network, locked, group_label, loop_label, group)
    } else {
      data.frame(
        text = character(), x = numeric(), y = numeric(), font = character(), size = numeric(),
        color = character(), fill = character(), angle = numeric(), alpha = numeric(), fontface = character(),
        math_expression = logical(), hjust = numeric(), vjust = numeric(), lavaan = logical(), network = logical(), locked = logical(),
        group_label = logical(), loop_label = logical(), group = character(), stringsAsFactors = FALSE
      )
    }
  } else {
    data.frame(
      text = character(), x = numeric(), y = numeric(), font = character(), size = numeric(),
      color = character(), fill = character(), angle = numeric(), alpha = numeric(), fontface = character(),
      math_expression = logical(), hjust = numeric(), vjust = numeric(), lavaan = logical(), network = logical(), locked = logical(),
      group_label = logical(), loop_label = logical(), group = character(), stringsAsFactors = FALSE
    )
  }


  if (modify_params_edgelabel) {
    weight_annotations <- apply_modifications(
      weight_annotations,
      modified_edgelabels,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fill", "size", "alpha", "angle", "font", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (modify_params_edgelabel_xy) {
    weight_annotations <- apply_modifications(
      weight_annotations,
      modified_edgelabels_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  if (is_bipartite) {
    group_selector <- if (apply_bipartite_annotations) !vertex_attr(graph)$type else vertex_attr(graph)$type
  } else {
    group_selector <- NULL
  }

  if (modify_params_edgelabel_text) {
    if (nrow(modified_edgelabels_text) > 0) {
      for (i in seq_len(nrow(modified_edgelabels_text))) {
        mod <- modified_edgelabels_text[i, ]
        edge_idx <- which(
          edges$source == mod$lhs &
            edges$target == mod$rhs
        )
        if (length(edge_idx) == 1) {
          weight_annotations$text[[edge_idx]] <- mod$text
          weight_annotations$math_expression[[edge_idx]] <- mod$math_expression
        }
      }
    }
  }

  annotations <- points |>
    mutate(
      text = if ("label" %in% colnames(nodes)) nodes$label else if ("node" %in% colnames(layout)) layout$node else NA, # Use node name as default text
      font = node_label_font,
      size = node_label_size,
      color = node_label_color,
      fill = NA,
      angle = 0,
      alpha = node_label_alpha,
      fontface = node_label_fontface,
      math_expression = FALSE,
      hjust = 0.5,
      vjust = 0.5,
      lavaan = FALSE,
      network = TRUE,
      locked = FALSE,
      group_label = FALSE,
      loop_label = FALSE,
      group = which_group,
    ) |> select(text, x, y, font, size, color, fill, angle, alpha, fontface, math_expression, hjust, vjust, lavaan, network, locked, group_label, loop_label, group) |>
    bind_rows(weight_annotations)


  if (!is.null(group_selector)) {
    network_annotations <- which(last_state$annotations$network == TRUE)
    network_annotations <- network_annotations[group_selector]
    last_state$annotations[, c('x', 'y')] <- annotations[, c('x', 'y')]
    last_state$annotations[network_annotations, ] <- annotations[group_selector,]
    annotations <- last_state$annotations
  }


  if (modify_params_nodelabel_xy) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }

  if (modify_params_nodelabel_text) {
    if (nrow(modified_nodelabels_text) > 0) {
      for (i in seq_len(nrow(modified_nodelabels_text))) {
        mod <- modified_nodelabels_text[i, ]
        node_idx <- which(
          layout$node == mod$text
        )
        if (length(node_idx) == 1) {
          annotations$text[[node_idx]] <- mod$nodelabel
          annotations$math_expression[[node_idx]] <- mod$math_expression
        }
      }
    }
  }

  if (modify_params_nodelabel) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "size", "alpha", "angle", "font", "fontface"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }

  points[c("x", "y")] <- lapply(points[c("x", "y")], round, 5)

  line_cols <- c("x_start", "y_start", "x_end", "y_end",
                 "ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2")
  lines[line_cols] <- lapply(lines[line_cols], round, 5)

  annotations[c("x", "y")] <- lapply(annotations[c("x", "y")], round, 5)


  list(points = as.data.frame(points), lines = as.data.frame(lines),
       annotations = as.data.frame(annotations))
}

adjust_edge_coordinates <- function(lines_df, edge_list, points_df, auto_endpoint_spacing = 0) {
  for (i in 1:nrow(lines_df)) {
    start_index <- edge_list[i, 1]
    end_index <- edge_list[i, 2]

    if (start_index == end_index) {
      #warning(paste("Skipping self-loop at row", i, "Node Index:", start_index))
      next
    }

    start_shape <- points_df$shape[start_index]
    end_shape <- points_df$shape[end_index]

    start_x <- points_df$x[start_index]
    start_y <- points_df$y[start_index]
    end_x <- points_df$x[end_index]
    end_y <- points_df$y[end_index]

    start_size <- points_df$size[start_index]
    end_size <- points_df$size[end_index]

    start_width_height_ratio <- points_df$width_height_ratio[start_index] %||% 1
    end_width_height_ratio <- points_df$width_height_ratio[end_index] %||% 1

    start_orientation <- points_df$orientation[start_index]
    end_orientation <- points_df$orientation[end_index]

    start_dimensions <- list(size = start_size, width_height_ratio = start_width_height_ratio)
    end_dimensions <- list(size = end_size, width_height_ratio = end_width_height_ratio)

    # Adjust the endpoint coordinates
    adjusted_coords <- adjust_endpoint(
      x1 = start_x, y1 = start_y,
      x2 = end_x, y2 = end_y,
      spacing = auto_endpoint_spacing,
      shape1 = start_shape, shape2 = end_shape,
      dimensions1 = start_dimensions,
      dimensions2 = end_dimensions,
      orientation1 = start_orientation, orientation2 = end_orientation
    )


    # Update lines_df with adjusted coordinates
    lines_df$x_start[i] <- adjusted_coords$x_start
    lines_df$y_start[i] <- adjusted_coords$y_start
    lines_df$x_end[i] <- adjusted_coords$x_end
    lines_df$y_end[i] <- adjusted_coords$y_end
  }
  return(lines_df)
}

#
find_intersection <- function(x_center, y_center, x_target, y_target,
                              size, width_height_ratio = 1, orientation = 0,
                              shape = "circle") {

  # Calculate the direction vector from center to target
  dx <- x_target - x_center
  dy <- y_target - y_center

  # Normalize the direction vector
  distance <- sqrt(dx^2 + dy^2)
  if (distance == 0) {
    return(list(x = x_center, y = y_center))
  }

  dx_norm <- dx / distance
  dy_norm <- dy / distance

  # Use the EXACT same calculations as draw_points
  min_size_factor <- 0.25
  scale_factor <- sqrt(2)

  # Calculate adjusted dimensions - EXACTLY as in draw_points
  adjusted_width <- switch(shape,
                           "circle" = size / scale_factor * min_size_factor,
                           "square" = size * sqrt(2) * min_size_factor,
                           "triangle" = size * sqrt(4 / sqrt(3)) * min_size_factor,
                           "rectangle" = size * min_size_factor * width_height_ratio,
                           "diamond" = size * 1.4 * sqrt(1.5) * min_size_factor * width_height_ratio,
                           "oval" = size * min_size_factor / scale_factor * width_height_ratio,
                           size / 3  # default
  )

  adjusted_height <- switch(shape,
                            "circle" = adjusted_width,
                            "square" = adjusted_width,
                            "triangle" = adjusted_width * sqrt(3) / 2,
                            "rectangle" = size * min_size_factor,
                            "diamond" = size * 1.4 * sqrt(1.5) * min_size_factor,
                            "oval" = size * min_size_factor / scale_factor,
                            adjusted_width / width_height_ratio  # default
  )

  coords <- switch(shape,
                   "circle" = {
                     t <- seq(0, 2 * pi, length.out = 100)
                     data.frame(
                       x = adjusted_width * cos(t),
                       y = adjusted_height * sin(t)
                     )
                   },
                   "square" = data.frame(
                     x = c(-adjusted_width/2, adjusted_width/2, adjusted_width/2, -adjusted_width/2),
                     y = c(-adjusted_height/2, -adjusted_height/2, adjusted_height/2, adjusted_height/2)
                   ),
                   "triangle" = data.frame(
                     x = c(0, -adjusted_width/2, adjusted_width/2),
                     y = c(-adjusted_height/2, adjusted_height/2, adjusted_height/2)
                   ),
                   "rectangle" = data.frame(
                     x = c(-adjusted_width/2, adjusted_width/2, adjusted_width/2, -adjusted_width/2),
                     y = c(-adjusted_height/2, -adjusted_height/2, adjusted_height/2, adjusted_height/2)
                   ),
                   "diamond" = data.frame(
                     x = c(0, -adjusted_width/2, 0, adjusted_width/2),
                     y = c(adjusted_height/2, 0, -adjusted_height/2, 0)
                   ),
                   "oval" = {
                     t <- seq(0, 2 * pi, length.out = 100)
                     data.frame(
                       x = adjusted_width * cos(t),
                       y = adjusted_height * sin(t)
                     )
                   },
                   # Default to circle
                   {
                     t <- seq(0, 2 * pi, length.out = 100)
                     data.frame(
                       x = adjusted_width * cos(t),
                       y = adjusted_height * sin(t)
                     )
                   }
  )

  if (orientation != 0) {
    angle <- -orientation * pi / 180
    coords <- rotate_coords(coords$x, coords$y, orientation, cx = 0, cy = 0)
  }

  if (shape %in% c("circle", "oval")) {
    if (abs(dx_norm) > 0 || abs(dy_norm) > 0) {
      t <- 1 / sqrt((dx_norm/adjusted_width)^2 + (dy_norm/adjusted_height)^2)
      x_intersect_rot <- t * dx_norm
      y_intersect_rot <- t * dy_norm
    } else {
      x_intersect_rot <- 0
      y_intersect_rot <- 0
    }
  } else if (shape == "triangle") {
    # Simple edge intersection for triangle
    n_vertices <- 3
    t_values <- c()

    for (i in 1:n_vertices) {
      j <- ifelse(i == n_vertices, 1, i + 1)

      x1 <- coords$x[i]
      y1 <- coords$y[i]
      x2 <- coords$x[j]
      y2 <- coords$y[j]

      # Line-line intersection between ray and edge
      denom <- (x2 - x1) * dy_norm - (y2 - y1) * dx_norm
      if (abs(denom) < 1e-10) next  # Parallel lines

      t <- (x1 * (y2 - y1) - y1 * (x2 - x1)) / denom
      if (t <= 1e-10) next  # Intersection behind ray origin or too close

      # Check if intersection is within segment using barycentric coordinates
      intersect_x <- t * dx_norm
      intersect_y <- t * dy_norm

      # Vector from edge start to intersection point
      edge_vec <- c(x2 - x1, y2 - y1)
      point_vec <- c(intersect_x - x1, intersect_y - y1)

      # Project point onto edge
      edge_length_sq <- edge_vec[1]^2 + edge_vec[2]^2
      if (edge_length_sq < 1e-10) next

      dot_product <- point_vec[1] * edge_vec[1] + point_vec[2] * edge_vec[2]
      u <- dot_product / edge_length_sq

      if (u >= 0 && u <= 1) {
        t_values <- c(t_values, t)
      }
    }

    if (length(t_values) > 0) {
      t <- min(t_values)
      x_intersect_rot <- t * dx_norm
      y_intersect_rot <- t * dy_norm
    } else {
      # Final fallback: use bounding circle of the actual triangle bounds
      max_x <- max(abs(coords$x))
      max_y <- max(abs(coords$y))
      max_dim <- max(max_x, max_y)
      x_intersect_rot <- max_dim * dx_norm
      y_intersect_rot <- max_dim * dy_norm
    }
  } else {
    # For polygons, find intersection with edges
    n_vertices <- nrow(coords)
    t_values <- c()

    for (i in 1:n_vertices) {
      j <- ifelse(i == n_vertices, 1, i + 1)

      x1 <- coords$x[i]
      y1 <- coords$y[i]
      x2 <- coords$x[j]
      y2 <- coords$y[j]

      # Line-line intersection between ray and edge
      denom <- (x2 - x1) * dy_norm - (y2 - y1) * dx_norm
      if (abs(denom) < 1e-10) next  # Parallel lines

      t <- (x1 * (y2 - y1) - y1 * (x2 - x1)) / denom
      if (t <= 0) next  # Intersection behind ray origin

      # Check if intersection is within segment
      u <- ((t * dx_norm - x1) * (x2 - x1) + (t * dy_norm - y1) * (y2 - y1)) /
        ((x2 - x1)^2 + (y2 - y1)^2)

      if (u >= 0 && u <= 1) {
        t_values <- c(t_values, t)
      }
    }

    if (length(t_values) > 0) {
      t <- min(t_values)  # Closest intersection
      x_intersect_rot <- t * dx_norm
      y_intersect_rot <- t * dy_norm
    } else {
      # Fallback: use bounding circle
      max_dim <- max(adjusted_width, adjusted_height)
      x_intersect_rot <- (max_dim / 2) * dx_norm
      y_intersect_rot <- (max_dim / 2) * dy_norm
    }
  }

  if (orientation != 0) {
    angle <- orientation * pi / 180
    x_temp <- x_intersect_rot
    y_temp <- y_intersect_rot
    x_intersect_rot <- cos(angle) * x_temp - sin(angle) * y_temp
    y_intersect_rot <- sin(angle) * x_temp + cos(angle) * y_temp
  }

  x_intersect <- x_center + x_intersect_rot
  y_intersect <- y_center + y_intersect_rot

  return(list(x = x_intersect, y = y_intersect))
}

adjust_endpoint <- function(x1, y1, x2, y2, spacing = 0,
                            shape1 = "circle", shape2 = "circle",
                            dimensions1 = list(width_height_ratio = 1, size = 1),
                            dimensions2 = list(width_height_ratio = 1, size = 1),
                            orientation1 = 0, orientation2 = 0) {
  # Find intersections
  start_intersect <- find_intersection(
    x_center = x1, y_center = y1,
    x_target = x2, y_target = y2,
    size = dimensions1$size, width_height_ratio = dimensions1$width_height_ratio,
    orientation = orientation1, shape = shape1
  )

  x1 <- start_intersect$x
  y1 <- start_intersect$y

  end_intersect <- find_intersection(
    x_center = x2, y_center = y2,
    x_target = x1, y_target = y1,
    size = dimensions2$size, width_height_ratio = dimensions2$width_height_ratio,
    orientation = orientation2, shape = shape2
  )

  x2 <- end_intersect$x
  y2 <- end_intersect$y

  # Apply spacing adjustment
  if (spacing > 0) {
    dx <- x2 - x1
    dy <- y2 - y1
    distance <- sqrt(dx^2 + dy^2)
    if (distance > 0) {
      dx <- dx / distance
      dy <- dy / distance
      x1 <- x1 + spacing * dx
      y1 <- y1 + spacing * dy
      x2 <- x2 - spacing * dx
      y2 <- y2 - spacing * dy
    }
  }

  return(list(x_start = x1, y_start = y1, x_end = x2, y_end = y2))
}

interpolate_points <- function(x_start, y_start, x_end, y_end, n = 100) {
  t <- seq(0, 1, length.out = n)
  x <- (1 - t) * x_start + t * x_end
  y <- (1 - t) * y_start + t * y_end
  data.frame(x = x, y = y)
}

rotate_coords <- function(x, y, angle, cx = 0, cy = 0) {
  angle_rad <- angle * pi / 180
  x_rot <- cos(angle_rad) * (x - cx) - sin(angle_rad) * (y - cy) + cx
  y_rot <- sin(angle_rad) * (x - cx) + cos(angle_rad) * (y - cy) + cy
  list(x = x_rot, y = y_rot)
}


calculate_control_point <- function(x_start, y_start, x_end, y_end,
                                    curvature_magnitude = 0.3,
                                    rotate_curvature = FALSE,
                                    curvature_asymmetry = 0,
                                    center_x = NULL, center_y = NULL) {
  mid_x <- (x_start + x_end) / 2
  mid_y <- (y_start + y_end) / 2
  dx <- x_end - x_start
  dy <- y_end - y_start
  seg_length <- sqrt(dx^2 + dy^2)

  if (is.null(center_x) || is.null(center_y)) {
    perp_x <- -dy/seg_length
    perp_y <- dx/seg_length
  } else {
    center_to_mid_x <- mid_x - center_x
    center_to_mid_y <- mid_y - center_y
    center_to_mid_length <- sqrt(center_to_mid_x^2 + center_to_mid_y^2)

    if (center_to_mid_length > 0) {
      outward_x <- center_to_mid_x / center_to_mid_length
      outward_y <- center_to_mid_y / center_to_mid_length

      perp_x <- -dy/seg_length
      perp_y <- dx/seg_length

      dot_product <- perp_x * outward_x + perp_y * outward_y

      if (dot_product < 0) {
        perp_x <- -perp_x
        perp_y <- -perp_y
      }
    } else {
      perp_x <- -dy/seg_length
      perp_y <- dx/seg_length
    }
  }

  ctrl1_x <- x_start + dx/3 + perp_x * curvature_magnitude * seg_length * (1 + curvature_asymmetry)
  ctrl1_y <- y_start + dy/3 + perp_y * curvature_magnitude * seg_length * (1 + curvature_asymmetry)
  ctrl2_x <- x_end - dx/3 + perp_x * curvature_magnitude * seg_length * (1 - curvature_asymmetry)
  ctrl2_y <- y_end - dy/3 + perp_y * curvature_magnitude * seg_length * (1 - curvature_asymmetry)

  if (rotate_curvature) {
    ctrl1_x <- 2 * mid_x - ctrl1_x
    ctrl1_y <- 2 * mid_y - ctrl1_y
    ctrl2_x <- 2 * mid_x - ctrl2_x
    ctrl2_y <- 2 * mid_y - ctrl2_y

    temp_x <- ctrl1_x
    temp_y <- ctrl1_y
    ctrl1_x <- ctrl2_x
    ctrl1_y <- ctrl2_y
    ctrl2_x <- temp_x
    ctrl2_y <- temp_y
  }

  list(
    ctrl_x = ctrl1_x,
    ctrl_y = ctrl1_y,
    ctrl_x2 = ctrl2_x,
    ctrl_y2 = ctrl2_y
  )
}

find_peak_point <- function(bezier_points, x_start, y_start, x_end, y_end) {
  A <- y_end - y_start
  B <- x_start - x_end
  C <- (x_end * y_start) - (x_start * y_end)

  distances <- abs(A * bezier_points$x + B * bezier_points$y + C) / sqrt(A^2 + B^2)

  peak_idx <- which.max(distances)

  return(peak_idx)
}

calculate_curvature_magnitude <- function(x_start, y_start, x_end, y_end,
                                          ctrl_x, ctrl_y, ctrl_x2, ctrl_y2) {

  mid_x <- (x_start + x_end) / 2
  mid_y <- (y_start + y_end) / 2


  dx <- x_end - x_start
  dy <- y_end - y_start
  seg_length <- sqrt(dx^2 + dy^2)

  if (seg_length == 0) {
    return(0)
  }

  perp_x <- -dy / seg_length
  perp_y <- dx / seg_length

  expected_ctrl1_x <- x_start + dx/3 + perp_x * seg_length
  expected_ctrl1_y <- y_start + dy/3 + perp_y * seg_length
  expected_ctrl2_x <- x_end - dx/3 + perp_x * seg_length
  expected_ctrl2_y <- y_end - dy/3 + perp_y * seg_length

  vec_ctrl1_x <- ctrl_x - (x_start + dx/3)
  vec_ctrl1_y <- ctrl_y - (y_start + dy/3)
  vec_ctrl2_x <- ctrl_x2 - (x_end - dx/3)
  vec_ctrl2_y <- ctrl_y2 - (y_end - dy/3)

  proj_ctrl1 <- vec_ctrl1_x * perp_x + vec_ctrl1_y * perp_y
  proj_ctrl2 <- vec_ctrl2_x * perp_x + vec_ctrl2_y * perp_y

  curvature_magnitude <- (abs(proj_ctrl1) + abs(proj_ctrl2)) / (2 * seg_length)

  if ((proj_ctrl1 + proj_ctrl2) < 0) {
    curvature_magnitude <- -curvature_magnitude
  }

  return(curvature_magnitude)
}

calculate_asymmetry <- function(x_start, y_start, x_end, y_end,
                                ctrl_x, ctrl_y, ctrl_x2, ctrl_y2) {

  dx <- x_end - x_start
  dy <- y_end - y_start
  seg_length <- sqrt(dx^2 + dy^2)

  if (seg_length == 0) {
    return(0)
  }

  perp_x <- -dy / seg_length
  perp_y <- dx / seg_length

  expected_ctrl1_x <- x_start + dx/3
  expected_ctrl1_y <- y_start + dy/3
  expected_ctrl2_x <- x_end - dx/3
  expected_ctrl2_y <- y_end - dy/3

  dev1_x <- ctrl_x - expected_ctrl1_x
  dev1_y <- ctrl_y - expected_ctrl1_y
  dev2_x <- ctrl_x2 - expected_ctrl2_x
  dev2_y <- ctrl_y2 - expected_ctrl2_y

  proj1 <- dev1_x * perp_x + dev1_y * perp_y
  proj2 <- dev2_x * perp_x + dev2_y * perp_y

  curvature_magnitude <- (abs(proj1) + abs(proj2)) / (2 * seg_length)

  if (curvature_magnitude == 0) {
    return(0)
  }

  asymmetry <- (proj1 - proj2) / (2 * curvature_magnitude * seg_length)

  return(asymmetry)
}


create_bezier_curve <- function(x_start, y_start,
                                x_end, y_end,
                                ctrl_x, ctrl_y,
                                ctrl_x2, ctrl_y2,
                                n_points = 100) {

  t <- seq(0, 1, length.out = n_points)

  bezier_x <- (1-t)^3*x_start + 3*(1-t)^2*t*ctrl_x + 3*(1-t)*t^2*ctrl_x2 + t^3*x_end
  bezier_y <- (1-t)^3*y_start + 3*(1-t)^2*t*ctrl_y + 3*(1-t)*t^2*ctrl_y2 + t^3*y_end

  data.frame(x = bezier_x, y = bezier_y)
}


blavaan_to_lavstring <- function(fit) {
  if (is(fit)[[1]] != "blavaan") {
    stop("Input must be a blavaan object")
  }

  return(reconstruct_from_parTable(fit))
}

fit_to_lavstring <- function(fit) {
  if (!inherits(fit, "lavaan")) {
    stop("Input must be a lavaan object (class 'lavaan').")
  }

  # Try to extract from call first (preserves original formatting)
  model_string <- tryCatch({
    if (is.name(fit@call$model)) {
      # Case 1: Model stored in a variable
      eval(fit@call$model, envir = environment(fit@call))
    } else if (is.character(fit@call$model)) {
      # Case 2: Model passed directly as string
      as.character(fit@call$model)
    } else {
      # Case 3: Fall back to reconstruction
      stop("Cannot extract from call")
    }
  }, error = function(e) {
    # Fallback: reconstruct from parameter table (works with tidySEM)
    reconstruct_from_parTable(fit)
  })

  return(model_string)
}

reconstruct_from_parTable <- function(fit) {
  param_table <- lavaan::parTable(fit)
  lines <- character()

  # Get all observed variables to check for duplicates
  all_observed <- unique(c(
    param_table$rhs[param_table$op == "=~"],  # Indicators in measurement model
    param_table$lhs[param_table$op == "~"],   # Dependent variables in regressions
    param_table$rhs[param_table$op == "~"],   # Independent variables in regressions
    param_table$lhs[param_table$op == "~~" & param_table$lhs == param_table$rhs],  # Variances
    param_table$lhs[param_table$op == "~1"]   # Variables with intercepts
  ))

  # Remove empty strings and latent variables (those with =~ operator)
  latent_vars <- unique(param_table$lhs[param_table$op == "=~"])
  all_observed <- setdiff(all_observed, c("", latent_vars))

  # Check for duplicates
  if (any(duplicated(all_observed))) {
    warning("Duplicate observed variables found: ",
            paste(unique(all_observed[duplicated(all_observed)]), collapse = ", "))
  }

  # 1. Measurement model (==)
  loadings <- param_table[param_table$op == "=~", ]
  if (nrow(loadings) > 0) {
    factors <- unique(loadings$lhs)
    for (factor in factors) {
      indicators <- loadings$rhs[loadings$lhs == factor]
      indicators <- unique(indicators)
      lines <- c(lines, paste(factor, "=~", paste(indicators, collapse = " + ")))
    }
  }

  # 2. Structural model (~)
  regressions <- param_table[param_table$op == "~", ]
  if (nrow(regressions) > 0) {
    regressions <- regressions[!duplicated(regressions[, c("lhs", "rhs")]), ]
    for (i in 1:nrow(regressions)) {
      lines <- c(lines, paste(regressions$lhs[i], "~", regressions$rhs[i]))
    }
  }

  # 3. Covariances (~~) - with duplicate removal
  covariances <- param_table[param_table$op == "~~" & param_table$lhs != param_table$rhs, ]
  if (nrow(covariances) > 0) {
    unique_covs <- character()
    for (i in 1:nrow(covariances)) {
      pair <- sort(c(covariances$lhs[i], covariances$rhs[i]))
      pair_str <- paste(pair[1], "~~", pair[2])
      if (!pair_str %in% unique_covs) {
        unique_covs <- c(unique_covs, pair_str)
      }
    }
    lines <- c(lines, unique_covs)
  }

  # 4. Intercepts (~1)
  intercepts <- param_table[param_table$op == "~1" & !param_table$lhs %in% c("", ".p."), ]
  if (nrow(intercepts) > 0) {
    intercepts <- intercepts[!duplicated(intercepts$lhs), ]
    for (i in 1:nrow(intercepts)) {
      lines <- c(lines, paste(intercepts$lhs[i], "~ 1"))
    }
  }

  # 5. Variances (~~ with same variable) - only if user-specified or fixed
  variances <- param_table[param_table$op == "~~" & param_table$lhs == param_table$rhs, ]
  if (nrow(variances) > 0) {
    variances <- variances[!duplicated(variances$lhs), ]
    for (i in 1:nrow(variances)) {
      lines <- c(lines, paste(variances$lhs[i], "~~", variances$rhs[i]))
    }
  }

  # Remove any duplicate lines that might have been created
  lines <- unique(lines)

  return(paste(lines, collapse = "\n"))
}


lavstring_to_fit <- function(lavaan_string, sem_code = NULL, data_file = NULL, multi_group = FALSE,
                             group_var = NULL, invariance_level = NULL, custom_sem = TRUE,
                             model_type = "sem", nfactors = NULL) {

  if (multi_group) custom_sem = FALSE

  if (is.null(data_file)) {
    stop("Data file must be provided")
  }

  model <- lavaan::lavaanify(lavaan_string)
  latent_vars <- unique(model$lhs[model$op == "=~"])    # Latent variables
  observed_vars <- unique(setdiff(model$rhs[model$op %in% c("=~", "~", "~~")], model$lhs[model$op == "=~"]))

  data <- data_file

  if (!is.null(sem_code) && custom_sem) {
    # Check for required components in custom code
    if (!grepl("lavaan_string", sem_code) || !grepl("data", sem_code)) {
      stop("Custom SEM code must include `lavaan_string` and `data`.")
    }

    allowed_functions <- c("sem", "cfa", "growth", "efa", "lavaan")
    function_found <- sapply(allowed_functions, function(fun) grepl(paste0("\\b", fun, "\\("), sem_code))

    if (!any(function_found)) {
      stop("Custom SEM code must use a valid lavaan function: ", paste(allowed_functions, collapse = ", "))
    }
  }

  fit <- tryCatch({
    if (custom_sem && !is.null(sem_code)) {
      # Use custom SEM code
      eval(parse(text = sem_code))
    } else {
      if (multi_group) {
        if (is.null(group_var)) {
          stop("group_var must be specified for multi-group analysis")
        }

        group_equal_args <- switch(invariance_level,
                                   "configural" = NULL,
                                   "metric" = "loadings",
                                   "scalar" = c("loadings", "intercepts"),
                                   "strict" = c("loadings", "intercepts", "residuals"),
                                   NULL)

        # Choose appropriate function based on model type
        switch(model_type,
               "sem" = sem(lavaan_string, data = data, group = group_var, group.equal = group_equal_args),
               "cfa" = cfa(lavaan_string, data = data, group = group_var, group.equal = group_equal_args),
               "growth" = growth(lavaan_string, data = data, group = group_var, group.equal = group_equal_args),
               "efa" = {
                 warning("EFA typically doesn't support multi-group analysis. Using CFA instead.")
                 cfa(lavaan_string, data = data, group = group_var, group.equal = group_equal_args)
               },
               # Default to sem
               sem(lavaan_string, data = data, group = group_var, group.equal = group_equal_args)
        )
      } else {
        # Single-group analysis
        switch(model_type,
               "sem" = sem(lavaan_string, data = data),
               "cfa" = cfa(lavaan_string, data = data),
               "growth" = growth(lavaan_string, data = data),
               "efa" = {
                 if (is.null(nfactors)) {
                   stop("nfactors must be specified for EFA models")
                 }
                 efa(lavaan_string, data = data, nfactors = nfactors)
               },
               sem(lavaan_string, data = data)
        )
      }
    }
  }, error = function(e) {
    stop("Error in SEM model fitting: ", e$message)
  })

  return(fit)
}


lavaan_to_sempaths <- function(fit, data_file = NULL, layout_algorithm = 'tree2',
                               intercepts = TRUE,
                               annotate_estimates = TRUE,
                               standardized = FALSE,
                               unstandardized = TRUE,
                               conf_int = FALSE,
                               p_val = FALSE,
                               multi_group = FALSE,
                               group_var = NULL,
                               group_level = NULL,
                               p_val_alpha = 0.05,
                               combine = FALSE,
                               group1 = NULL,
                               group2 = NULL,
                               sep_by = " |",
                               residuals = FALSE) {

  if (is.list(fit)) {
    combine <- FALSE
    fake_combine <- TRUE
  } else {
    fake_combine <- FALSE
  }

  if (is.null(group1) || is.null(group2)) combine <- FALSE

  if (combine) {
    params <- get_comparison_table(fit = fit, alpha = p_val_alpha, group1 = group1, group2 = group2, sep_by = sep_by)

    group_info <- lavInspect(fit, "group.label")
    group_number <- which(group_info == group_level)

    edge_params <- params[params$op %in% c("=~", "~1", "~~", "~"), ]
    self_loop_indices <- which(edge_params$lhs == edge_params$rhs)

    if (!residuals) {
      if (length(self_loop_indices) > 0) {
        params1 <- edge_params[-self_loop_indices, ]
        std_est1 <- standardizedSolution(fit)[-self_loop_indices, ]
      } else {
        params1 <- edge_params
      }
    } else {
      params1 <- edge_params
      std_est1 <- standardizedSolution(fit)
    }

    if (!intercepts) params1 <- params1[params1$op != "~1", ]

    unstd <- params1$est  # Unstandardized
    std <- params1$std   # Standardized

    params1$pvalue[is.na(params1$pvalue)] <- 1
    std_est1$pvalue[is.na(std_est1$pvalue)] <- 1
    # pval_idx <- which(params1$pvalue < p_val_alpha)

    if (p_val == TRUE) {
      std[which(std_est1$pvalue < p_val_alpha)] <- paste0(std[which(std_est1$pvalue < p_val_alpha)], "*")
      unstd[which(params1$pvalue < p_val_alpha)] <- paste0(unstd[which(params1$pvalue < p_val_alpha)], "*")
    }

    ci_labels <- if (conf_int) {
      if ((standardized == TRUE && unstandardized == FALSE && conf_int == TRUE)) {
        ci_col <- params1$confint_std
      } else {
        ci_col <- params1$confint_unstd
      }

      if (!is.null(ci_col)) {
        ci_col_clean <- ifelse(is.na(ci_col) | grepl("NA", ci_col), "", ci_col)
        if (standardized == FALSE && unstandardized == FALSE) {
          ci_col_clean
        } else if (ci_col_clean != "") {
          paste0("\n", ci_col_clean)
        } else {
          ""
        }
      } else {
        ""
      }
    } else {
      ""
    }


    if (standardized == TRUE && unstandardized == TRUE) {
      base_labels <- paste0(unstd, " (", std, ")")
    } else if (standardized == TRUE && unstandardized == FALSE) {
      base_labels <- std
    } else if (standardized == FALSE && unstandardized == TRUE) {
      base_labels <- unstd
    } else {
      base_labels <- NULL
    }

    edgeLabels <- if (conf_int && !is.null(base_labels)) {
      paste0(base_labels, ci_labels)
    } else {
      base_labels
    }

    if (standardized == TRUE && unstandardized == FALSE) {
      edge_colors <- ifelse(std_est1$pvalue < p_val_alpha, "#000000", "#BEBEBE")
    } else {
      edge_colors <- ifelse(params1$pvalue < p_val_alpha, "#000000", "#BEBEBE")
    }


    sem_paths0 <- semPlot::semPaths(fit, layout = layout_algorithm, intercepts = intercepts, what = "paths",
                                    whatLabels = "par", edgeLabels = edgeLabels, residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                    edge.color = edge_colors)


    if (inherits(sem_paths0, 'list')) {
      sem_paths <- sem_paths0[[1]]
    } else if (is(sem_paths0) == 'qgraph') {
      sem_paths <- sem_paths0
    }


  } else if (fake_combine) {

    params <- combine_model_parameters(fit1 = fit[[1]], fit2 = fit[[2]], group1 = group1, group2 = group2, sep_by = sep_by)

    edge_params <- params[params$op %in% c("=~", "~1", "~~", "~"), ]
    self_loop_indices <- which(edge_params$lhs == edge_params$rhs)

    if (!residuals) {
      if (length(self_loop_indices) > 0) {
        params1 <- edge_params[-self_loop_indices, ]
        # std_est1 <- standardizedSolution(fit)[-self_loop_indices, ]
      } else {
        params1 <- edge_params
      }
    } else {
      params1 <- edge_params
      # std_est1 <- standardizedSolution(fit)
    }

    params1$pvalue <- 1 # fake column

    if (!intercepts) params1 <- params1[params1$op != "~1", ]

    unstd <- params1$est  # Unstandardized
    std <- params1$std   # Standardized

    ci_labels <- if (conf_int) {
      if ((standardized == TRUE && unstandardized == FALSE && conf_int == TRUE)) {
        ci_col <- params1$confint_std
      } else {
        ci_col <- params1$confint_unstd
      }

      if (!is.null(ci_col)) {
        ci_col_clean <- ifelse(is.na(ci_col) | grepl("NA", ci_col), "", ci_col)
        if (standardized == FALSE && unstandardized == FALSE) {
          ci_col_clean
        } else if (ci_col_clean != "") {
          paste0("\n", ci_col_clean)
        } else {
          ""
        }
      } else {
        ""
      }
    } else {
      ""
    }

    if (standardized == TRUE && unstandardized == TRUE) {
      base_labels <- paste0(unstd, " (", std, ")")
    } else if (standardized == TRUE && unstandardized == FALSE) {
      base_labels <- std
    } else if (standardized == FALSE && unstandardized == TRUE) {
      base_labels <- unstd
    } else {
      base_labels <- NULL
    }


    edgeLabels <- if (conf_int && !is.null(base_labels)) {
      paste0(base_labels, ci_labels)
    } else {
      base_labels
    }


    if (multi_group == TRUE) {
      sem_paths0 <- semPlot::semPaths(fit[[1]], layout = layout_algorithm, intercepts = intercepts, what = "paths",
                                      whatLabels = "par", edgeLabels = edgeLabels, residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                      edge.color = ifelse(params1$pvalue < p_val_alpha, "#000000", "#BEBEBE"))

      names(sem_paths0) <- group_info
      sem_paths <- sem_paths0[[1]]

    } else if (multi_group == FALSE) {
      if (!is.null(data_file)) {
        sem_paths <- semPlot::semPaths(fit[[1]], layout = layout_algorithm, intercepts = intercepts, what = "paths",
                                       whatLabels = "par", edgeLabels = edgeLabels, residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                       edge.color = ifelse(params1$pvalue < p_val_alpha, "#000000", "#BEBEBE"))
      } else {
        sem_paths <- semPlot::semPaths(fit[[1]], layout = layout_algorithm, intercepts = intercepts,
                                       what = "paths", whatLabels = "par", edgeLabels = edgeLabels,
                                       residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                       edge.color = ifelse(params1$pvalue < p_val_alpha, "#000000", "#BEBEBE"))
      }
    }

  } else {
    params <- lavaan::parameterEstimates(fit)
    edge_params <- params[params$op %in% c("=~", "~1", "~~", "~"), ]

    self_loop_indices <- which(edge_params$lhs == edge_params$rhs)

    # Filter parameters based on multi-group setting
    if (multi_group == TRUE && !is.null(group_level)) {
      # Get the group number for the specified group level
      group_info <- lavInspect(fit, "group.label")
      group_number <- which(group_info == group_level)

      if (length(group_number) == 0) {
        stop("Group level '", group_level, "' not found in the model. Available groups: ",
             paste(group_info, collapse = ", "))
      }

      if (!residuals) {
        if (length(self_loop_indices) > 0) {
          params1 <- edge_params[-self_loop_indices, ]
          std_est1 <- standardizedSolution(fit)[-self_loop_indices, ]
        } else {
          params1 <- edge_params
          std_est1 <- standardizedSolution(fit)
        }
      } else {
        params1 <- edge_params
        std_est1 <- standardizedSolution(fit)
      }

      # Filter parameters for the specific group
      params1 <- params1[params1$group == group_number, ]
      std_est1 <- std_est1[std_est1$group == group_number, ]

    } else {
      # Single group case
      if (!residuals) {
        if (length(self_loop_indices) > 0) {
          params1 <- edge_params[-self_loop_indices, ]
          std_est1 <- standardizedSolution(fit)[-self_loop_indices, ]
        } else {
          params1 <- edge_params
          std_est1 <- standardizedSolution(fit)
        }
      } else {
        params1 <- edge_params
        std_est1 <- standardizedSolution(fit)
      }
    }

    if (!intercepts) {
      params1 <- params1[params1$op != "~1", ]
      std_est1 <- std_est1[std_est1$op != "~1", ]
    }

    unstd <- round(params1$est, 2)  # Unstandardized
    std <- round(std_est1$est.std, 2)   # Standardized

    # Handle NA p-values
    params1$pvalue[is.na(params1$pvalue)] <- 1
    std_est1$pvalue[is.na(std_est1$pvalue)] <- 1


    # Apply significance stars based on which values are shown
    if (p_val == TRUE) {
      std[which(std_est1$pvalue < p_val_alpha)] <- paste0(std[which(std_est1$pvalue < p_val_alpha)], "*")
      unstd[which(params1$pvalue < p_val_alpha)] <- paste0(unstd[which(params1$pvalue < p_val_alpha)], "*")
    }

    if (standardized == TRUE && unstandardized == TRUE) {
      labels <- paste0(unstd, " (", std, ")")
    } else if (standardized == TRUE && unstandardized == FALSE) {
      labels <- std
    } else if (standardized == FALSE && unstandardized == TRUE) {
      labels <- unstd
    } else {
      labels <- NULL
    }

    if (conf_int) {
      get_std_ci <- (standardized == TRUE && unstandardized == FALSE && conf_int == TRUE)

      if (get_std_ci) {
        if (standardized == FALSE && unstandardized == FALSE) {
          ci_labels <- sapply(1:nrow(std_est1), function(i) {
            if (!is.na(std_est1$ci.lower[i]) && !is.na(std_est1$ci.upper[i])) {
              paste0("[", round(std_est1$ci.lower[i], 2), ", ",
                     round(std_est1$ci.upper[i], 2), "]")
            } else {
              ""
            }
          })
        } else {
          ci_labels <- sapply(1:nrow(std_est1), function(i) {
            if (!is.na(std_est1$ci.lower[i]) && !is.na(std_est1$ci.upper[i])) {
              paste0("\n[", round(std_est1$ci.lower[i], 2), ", ",
                     round(std_est1$ci.upper[i], 2), "]")
            } else {
              ""
            }
          })
        }
      } else {
        if (standardized == FALSE && unstandardized == FALSE) {
          ci_labels <- sapply(1:nrow(params1), function(i) {
            if (!is.na(params1$ci.lower[i]) && !is.na(params1$ci.upper[i])) {
              paste0("[", round(params1$ci.lower[i], 2), ", ",
                     round(params1$ci.upper[i], 2), "]")
            } else {
              ""
            }
          })
        } else {
          ci_labels <- sapply(1:nrow(params1), function(i) {
            if (!is.na(params1$ci.lower[i]) && !is.na(params1$ci.upper[i])) {
              paste0("\n[", round(params1$ci.lower[i], 2), ", ",
                     round(params1$ci.upper[i], 2), "]")
            } else {
              ""
            }
          })
        }
      }
    } else {
      ci_labels <- ""
    }

    edgeLabels <- switch(
      paste(standardized, unstandardized, conf_int, sep = "-"),
      "FALSE-TRUE-FALSE"  = unstd,
      "TRUE-FALSE-FALSE"  = std,
      "TRUE-TRUE-FALSE"   = labels,
      "FALSE-FALSE-FALSE" = labels,
      "FALSE-TRUE-TRUE"  = paste0(unstd, ci_labels),
      "TRUE-FALSE-TRUE"  = paste0(std, ci_labels),
      "TRUE-TRUE-TRUE"   = paste0(labels, ci_labels),
      "FALSE-FALSE-TRUE" = paste0(labels, ci_labels),
    )

    if (standardized == TRUE && unstandardized == FALSE) {
      edge_colors <- ifelse(std_est1$pvalue < p_val_alpha, "#000000", "#BEBEBE")
    } else {
      edge_colors <- ifelse(params1$pvalue < p_val_alpha, "#000000", "#BEBEBE")
    }

    if (multi_group == TRUE) {
      sem_paths0 <- semPlot::semPaths(fit, layout = layout_algorithm, intercepts = intercepts, what = "paths",
                                      whatLabels = "par", edgeLabels = edgeLabels, residuals = residuals,
                                      plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                      edge.color = edge_colors)

      names(sem_paths0) <- group_info
      sem_paths <- sem_paths0[[1]]

    } else if (multi_group == FALSE) {
      if (!is.null(data_file)) {
        sem_paths <- semPlot::semPaths(fit, layout = layout_algorithm, intercepts = intercepts, what = "paths",
                                       whatLabels = "par", edgeLabels = edgeLabels, residuals = residuals,
                                       plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                       edge.color = edge_colors)
      } else {
        sem_paths <- semPlot::semPaths(fit, layout = layout_algorithm, intercepts = intercepts,
                                       what = "paths", whatLabels = "par", edgeLabels = edgeLabels,
                                       residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                       edge.color = edge_colors)
      }
    }

  }

  return(sem_paths)
}

get_standardized_hdi <- function(fit, ci_level = 0.95) {
  std_draws <- standardizedPosterior(fit)

  std_est <- standardizedSolution(fit)

  if (!"group" %in% names(std_est)) {
    std_est$group <- 1
  }

  if (!"label" %in% names(std_est)) {
    std_est$label <- NA_character_
  }

  group_labels <- blavInspect(fit, "group.label")
  if (length(group_labels) == 0) {
    group_labels <- "Group1"
  }

  # Process each parameter from std_est to maintain order
  hdi_intervals <- purrr::map_dfr(1:nrow(std_est), function(i) {
    param <- std_est[i, ]

    current_label <- ifelse(!is.na(param$label) && !is.null(param$label) && param$label != "",
                            param$label,
                            NA_character_)
    current_group <- ifelse(!is.na(param$group) && !is.null(param$group),
                            param$group,
                            1)

    sample_cols <- colnames(std_draws)[1:min(10, ncol(std_draws))]
    is_single_group <- !any(grepl("\\.g[0-9]+$", sample_cols))

    patterns <- character(0)

    if (!is.na(current_label)) {
      if (is_single_group) {
        patterns <- c(patterns, current_label)
      } else {
        patterns <- c(patterns, paste0(current_label, ".g", current_group))
      }
    }

    human_readable <- paste0(param$lhs, param$op, param$rhs)
    if (is_single_group) {
      patterns <- c(patterns, human_readable)
    } else {
      patterns <- c(patterns, paste0(human_readable, ".g", current_group))
    }

    if (param$op == "~1") {
      intercept_format <- paste0(param$lhs, ".1")
      if (is_single_group) {
        patterns <- c(patterns, intercept_format)
      } else {
        patterns <- c(patterns, paste0(intercept_format, ".g", current_group))
      }
    }

    if (param$op == "~~") {
      double_dot <- paste0(param$lhs, "..", param$rhs)
      if (is_single_group) {
        patterns <- c(patterns, double_dot)
      } else {
        patterns <- c(patterns, paste0(double_dot, ".g", current_group))
      }
    }

    col_name <- NULL
    for (pattern in patterns) {
      if (pattern %in% colnames(std_draws)) {
        col_name <- pattern
        break
      }
    }

    if (is.null(col_name)) {
      clean_colnames <- gsub("\\.g[0-9]+$", "", colnames(std_draws))
      if (human_readable %in% clean_colnames) {
        idx <- which(clean_colnames == human_readable)
        col_name <- colnames(std_draws)[idx[1]]
      }
    }

    if (!is.null(col_name) && col_name %in% colnames(std_draws)) {
      # Get the posterior draws for this parameter
      param_draws <- std_draws[, col_name]

      # Calculate HDI
      hdi_result <- bayestestR::hdi(param_draws, ci = ci_level)

      # Calculate standardized estimate (posterior median)
      est_std <- median(param_draws)

      # Calculate posterior standard deviation
      post_sd <- sd(param_draws)

      # Calculate probability of direction
      if (param$op %in% c("=~", "~", "~~") && param$lhs != param$rhs) {
        prob_positive <- mean(param_draws > 0)
        prob_negative <- mean(param_draws < 0)
      } else {
        prob_positive <- NA
        prob_negative <- NA
      }

      data.frame(
        lhs = param$lhs,
        op = param$op,
        rhs = param$rhs,
        group = current_group,
        label = current_label,
        est_std = est_std,
        post_sd = post_sd,
        CI_low = hdi_result$CI_low,
        CI_high = hdi_result$CI_high,
        prob_positive = prob_positive,
        prob_negative = prob_negative,
        col_name = col_name,
        stringsAsFactors = FALSE
      )
    } else {
      # Return NA if parameter not found
      data.frame(
        lhs = param$lhs,
        op = param$op,
        rhs = param$rhs,
        group = current_group,
        label = current_label,
        est_std = NA,
        post_sd = NA,
        CI_low = NA,
        CI_high = NA,
        prob_positive = NA,
        prob_negative = NA,
        col_name = NA,
        stringsAsFactors = FALSE
      )
    }
  })

  # Format the results
  hdi_intervals <- hdi_intervals |>
    dplyr::mutate(
      # Format confidence interval
      conf_int_std = ifelse(is.na(CI_low) | is.na(CI_high),
                            NA_character_,
                            sprintf("[%.2f, %.2f]", CI_low, CI_high)),
      credible_interval = conf_int_std,
      # Add group name
      group_name = ifelse(!is.na(group) & group <= length(group_labels),
                          group_labels[group],
                          paste("Group", group)),
      # Format estimate with interval
      est_with_ci = ifelse(!is.na(est_std) & !is.na(conf_int_std),
                           sprintf("%.2f %s", est_std, conf_int_std),
                           NA_character_)
    ) |>
    dplyr::select(lhs, op, rhs, group, group_name, label,
                  est_std, post_sd, CI_low, CI_high, conf_int_std,
                  est_with_ci, prob_positive, prob_negative, credible_interval)

  return(hdi_intervals)
}

blavaan_to_sempaths <- function(fit, data_file = NULL, layout_algorithm = 'tree2',
                                intercepts = TRUE,
                                annotate_estimates = TRUE,
                                standardized = FALSE,
                                unstandardized = TRUE,
                                p_val = FALSE,
                                conf_int = FALSE,
                                multi_group = FALSE,
                                group_var = NULL,
                                group_level = NULL,
                                combine = FALSE,
                                group1 = NULL,
                                group2 = NULL,
                                sep_by = " | ",
                                residuals = FALSE) {

  if (is.list(fit)) {
    combine <- FALSE
    fake_combine <- TRUE
  } else {
    fake_combine <- FALSE
  }

  if ((standardized == TRUE && unstandardized == FALSE && conf_int == TRUE)) {
    get_std_ci <- TRUE
  } else {
    get_std_ci <- FALSE
  }

  if (is.null(group1) || is.null(group2)) combine <- FALSE

  if (combine) {
    params <- get_comparison_table_bayes(fit = fit, rope = c(-0.1,0.1), group1 = group1, group2 = group2, sep_by = sep_by, get_std_ci = get_std_ci)

    group_info <- blavaan::blavInspect(fit, "group.label")

    edge_params <- params[params$op %in% c("=~", "~1", "~~", "~"), ]
    self_loop_indices <- which(edge_params$lhs == edge_params$rhs)

    if (!residuals) {
      if (length(self_loop_indices) > 0) {
        params1 <- edge_params[-self_loop_indices, ]
      } else {
        params1 <- edge_params
      }
    } else {
      params1 <- edge_params
    }

    if (!intercepts) {
      params1 <- params1[params1$op != "~1", ]
    }

    unstd <- params1$est  # Unstandardized comparison
    std <- params1$std   # Standardized comparison

    significant <- params1$significant
    significant[is.na(significant)] <- FALSE

    if (p_val == TRUE) {
      std[significant] <- paste0(std[significant], "*")
      unstd[significant] <- paste0(unstd[significant], "*")
    }

    ci_labels <- if (conf_int) {
      if ((standardized == TRUE && unstandardized == FALSE && conf_int == TRUE)) {
        ci_col <- params1$confint_std
      } else {
        ci_col <- params1$confint_unstd
      }

      if (!is.null(ci_col)) {
        ci_col_clean <- ifelse(is.na(ci_col) | grepl("NA", ci_col), "", ci_col)

        if (standardized == FALSE && unstandardized == FALSE) {
          ci_col_clean
        } else if (ci_col_clean != "") {
          paste0("\n", ci_col_clean)
        } else {
          ""
        }
      } else {
        ""
      }
    } else {
      ""
    }

    if (standardized == TRUE && unstandardized == TRUE) {
      base_labels <- paste0(unstd, " (", std, ")")
    } else if (standardized == TRUE && unstandardized == FALSE) {
      base_labels <- std
    } else if (standardized == FALSE && unstandardized == TRUE) {
      base_labels <- unstd
    } else {
      base_labels <- NULL
    }

    edgeLabels <- if (conf_int && !is.null(base_labels)) {
      paste0(base_labels, ci_labels)
    } else {
      base_labels
    }

    sem_paths0 <- semPlot::semPaths(fit, layout = layout_algorithm, intercepts = intercepts, what = "paths",
                                    whatLabels = "par", edgeLabels = edgeLabels, residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                    edge.color = ifelse(significant, "#000000", "#BEBEBE"))

    if (inherits(sem_paths0, 'list')) {
      sem_paths <- sem_paths0[[1]]
    } else if (inherits(sem_paths0, 'qgraph')) {
      sem_paths <- sem_paths0
    }

  } else if (fake_combine) {
    params <- combine_model_parameters_bayes(fit1 = fit[[1]], fit2 = fit[[2]], group1 = group1, group2 = group2, sep_by = sep_by, get_std_ci = get_std_ci)

    ref <- lavaan::parameterEstimates(fit[[1]])
    ref <- ref |> mutate(across(c(lhs, op, rhs), as.character))

    params <- ref |>
      select(lhs, op, rhs) |>
      left_join(params, by = c("lhs", "op", "rhs"))

    edge_params <- params[params$op %in% c("=~", "~1", "~~", "~"), ]
    self_loop_indices <- which(edge_params$lhs == edge_params$rhs)

    if (!residuals) {
      if (length(self_loop_indices) > 0) {
        params1 <- edge_params[-self_loop_indices, ]
      } else {
        params1 <- edge_params
      }
    } else {
      params1 <- edge_params
    }

    # Create fake significance column for compatibility
    params1$significant <- FALSE

    if (!intercepts) {
      params1 <- params1[params1$op != "~1", ]
    }

    unstd <- params1$est  # Unstandardized comparison
    std <- params1$std   # Standardized comparison

    significant <- params1$significant
    significant[is.na(significant)] <- FALSE

    if (p_val == TRUE) {
      std[significant] <- paste0(std[significant], "*")
      unstd[significant] <- paste0(unstd[significant], "*")
    }

    ci_labels <- if (conf_int) {
      if ((standardized == TRUE && unstandardized == FALSE && conf_int == TRUE)) {
        ci_col <- params1$confint_std
      } else {
        ci_col <- params1$confint_unstd
      }

      if (!is.null(ci_col)) {
        ci_col_clean <- ifelse(is.na(ci_col) | grepl("NA", ci_col), "", ci_col)
        if (standardized == FALSE && unstandardized == FALSE) {
          ci_col_clean
        } else if (ci_col_clean != "") {
          paste0("\n", ci_col_clean)
        } else {
          ""
        }
      } else {
        ""
      }
    } else {
      ""
    }

    if (standardized == TRUE && unstandardized == TRUE) {
      base_labels <- paste0(unstd, " (", std, ")")
    } else if (standardized == TRUE && unstandardized == FALSE) {
      base_labels <- std
    } else if (standardized == FALSE && unstandardized == TRUE) {
      base_labels <- unstd
    } else {
      base_labels <- NULL
    }

    edgeLabels <- if (conf_int && !is.null(base_labels)) {
      paste0(base_labels, ci_labels)
    } else {
      base_labels
    }

    sem_paths <- semPlot::semPaths(fit[[1]], layout = layout_algorithm, intercepts = intercepts, what = "paths",
                                   whatLabels = "par", edgeLabels = edgeLabels, residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                   edge.color = ifelse(significant, "#000000", "#BEBEBE"))

  } else {
    params <- lavaan::parameterEstimates(fit)
    edge_params <- params[params$op %in% c("=~", "~1", "~~", "~"), ]

    hpd_intervals <- as.data.frame(blavaan::blavInspect(fit, "hpd"))
    hpd_names <- rownames(hpd_intervals)

    self_loop_indices <- which(edge_params$lhs == edge_params$rhs)

    if (multi_group == TRUE && !is.null(group_level)) {
      group_info <- blavaan::blavInspect(fit, "group.label")

      if (!is.null(group_level)) {
        group_number <- which(group_info == group_level)
        if (length(group_number) == 0) {
          stop("Group level '", group_level, "' not found. Available groups: ",
               paste(group_info, collapse = ", "))
        }
      } else {
        group_number <- 1
        group_level <- group_info[1]
      }

      is_blavaan_format <- any(grepl("^\\.p[0-9]+\\.$", hpd_names)) ||
        any(grepl("\\.\\.", hpd_names))

      if (!residuals) {
        if (length(self_loop_indices) > 0) {
          params1 <- edge_params[-self_loop_indices, ]
          std_est1 <- standardizedSolution(fit)[-self_loop_indices, ]
        } else {
          params1 <- edge_params
          std_est1 <- standardizedSolution(fit)
        }
      } else {
        params1 <- edge_params
        std_est1 <- standardizedSolution(fit)
      }

      params1 <- params1[params1$group == group_number, ]
      std_est1 <- std_est1[std_est1$group == group_number, ]

      if (is_blavaan_format) {
        if (group_number == 1) {
          # Group 1: parameters without .gX suffix or ..1 suffix
          group_hpd_indices <- which(!grepl("\\.g[0-9]+$", hpd_names) &
                                       !grepl("\\.\\.1$", hpd_names) &
                                       !grepl("\\.1\\.g[0-9]+$", hpd_names))
        } else {
          # Group 2+: parameters with .gX or ..1 suffix
          group_hpd_pattern <- paste0("(\\.g", group_number, "$|\\.\\.", group_number, "$)")
          group_hpd_indices <- grep(group_hpd_pattern, hpd_names)
        }

        if (length(group_hpd_indices) > 0) {
          hpd_intervals <- hpd_intervals[group_hpd_indices, ]
          hpd_names <- rownames(hpd_intervals)
        }
      }

    } else {
      # Single group case
      group_number <- 1

      if (!residuals) {
        if (length(self_loop_indices) > 0) {
          params1 <- edge_params[-self_loop_indices, ]
          std_est1 <- standardizedSolution(fit)[-self_loop_indices, ]
        } else {
          params1 <- edge_params
          std_est1 <- standardizedSolution(fit)
        }
      } else {
        params1 <- edge_params
        std_est1 <- standardizedSolution(fit)
      }
    }

    if (!intercepts) {
      params1 <- params1[params1$op != "~1", ]
      std_est1 <- std_est1[std_est1$op != "~1", ]
    }

    unstd <- round(params1$est, 2)  # Unstandardized
    std <- round(std_est1$est.std, 2)   # Standardized

    significant <- rep(FALSE, nrow(params1))
    ci_labels <- character(nrow(params1))

    for(i in 1:nrow(params1)) {
      hpd_name <- NULL
      if (multi_group == TRUE &&!is.null(group_level)) {
        if (!is.na(params1$label[i]) && params1$label[i] != "") {
          base_hpd_name <- params1$label[i]

          if ((multi_group == TRUE && !is.null(group_level)) &&
              any(grepl("^\\.p[0-9]+\\.$", hpd_names))) {

            if (group_number == 1) {
              hpd_name <- base_hpd_name
            } else {
              possible_names <- c(
                paste0(base_hpd_name, "..", group_number),
                paste0(base_hpd_name, ".g", group_number),
                base_hpd_name
              )

              for (possible_name in possible_names) {
                if (possible_name %in% hpd_names) {
                  hpd_name <- possible_name
                  break
                }
              }
            }
          } else {
            hpd_name <- base_hpd_name
          }

        } else if (params1$op[i] == "~~") {
          lhs <- params1$lhs[i]
          rhs <- params1$rhs[i]

          if (any(grepl("\\.\\.", hpd_names))) {
            base_hpd_name <- paste0(lhs, "..", rhs)

            if ((multi_group == TRUE && !is.null(group_level)) && group_number > 1) {
              # Try with .g2 suffix first
              possible_names <- c(
                paste0(base_hpd_name, ".g", group_number),
                base_hpd_name  # Also try without suffix
              )

              for (possible_name in possible_names) {
                if (possible_name %in% hpd_names) {
                  hpd_name <- possible_name
                  break
                }
              }
            } else {
              hpd_name <- base_hpd_name
            }
          } else {
            # Human-readable format: x1~~x1
            hpd_name <- paste0(lhs, "~~", rhs)
          }

        } else if (params1$op[i] == "~1") {
          if ((multi_group == TRUE && !is.null(group_level)) &&
              group_number > 1 &&
              params1$lhs[i] %in% c("visual", "textual", "speed")) {
            hpd_name <- paste0(params1$lhs[i], ".1.g", group_number)
          } else if (!is.na(params1$label[i]) && params1$label[i] != "") {
            base_hpd_name <- params1$label[i]

            if ((multi_group == TRUE && !is.null(group_level)) && group_number > 1) {
              possible_names <- c(
                paste0(base_hpd_name, "..", group_number),
                paste0(base_hpd_name, ".g", group_number),
                base_hpd_name
              )

              for (possible_name in possible_names) {
                if (possible_name %in% hpd_names) {
                  hpd_name <- possible_name
                  break
                }
              }
            } else {
              hpd_name <- base_hpd_name
            }
          }
        }
      }


      if (is.null(hpd_name) || !(hpd_name %in% hpd_names)) {
        human_readable_name <- paste0(params1$lhs[i], params1$op[i],
                                      ifelse(params1$op[i] == "~1", "", params1$rhs[i]))
        if (human_readable_name %in% hpd_names) {
          hpd_name <- human_readable_name
        }
      }

      if (!is.null(hpd_name) && hpd_name %in% hpd_names) {
        match_idx <- which(hpd_names == hpd_name)
        significant[i] <- hpd_intervals$lower[match_idx] > 0 | hpd_intervals$upper[match_idx] < 0
        ci_labels[i] <- paste0("[",
                               round(hpd_intervals$lower[match_idx], 2), ", ",
                               round(hpd_intervals$upper[match_idx], 2), "]")
      }
    }

    for(i in 1:length(ci_labels)) {
      if (ci_labels[i] == "" && i <= length(unstd)) {
        ci_labels[i] <- paste0("[", round(unstd[i], 2), " ", round(unstd[i], 2), "]")
      }
    }

    conf_int_std1 <- NULL # initialize

    if (get_std_ci == TRUE) {
      get_standardized_hdi(bfit) -> conf_int_std
      conf_int_std1 <- conf_int_std[conf_int_std$group == group_number, ]
      ci_labels <- conf_int_std1$credible_interval
    }

    if (p_val == TRUE) {
      std[significant] <- paste0(std[significant], "*")
      unstd[significant] <- paste0(unstd[significant], "*")
    }

    if (standardized == TRUE && unstandardized == TRUE) {
      labels <- paste0(unstd, " (", std, ")")
    } else if (standardized == TRUE && unstandardized == FALSE) {
      labels <- std
    } else if (standardized == FALSE && unstandardized == TRUE) {
      labels <- unstd
    } else {
      labels <- NULL
    }

    edgeLabels <- switch(
      paste(standardized, unstandardized, conf_int, sep = "-"),
      "FALSE-TRUE-FALSE"  = unstd,
      "TRUE-FALSE-FALSE"  = std,
      "TRUE-TRUE-FALSE"   = labels,
      "FALSE-FALSE-FALSE" = labels,
      "FALSE-TRUE-TRUE"  = paste0(unstd, "\n", ci_labels),
      "TRUE-FALSE-TRUE"  = paste0(std, "\n",  ci_labels),
      "TRUE-TRUE-TRUE"   = paste0(labels, "\n",  ci_labels),
      "FALSE-FALSE-TRUE" = paste0(labels, ci_labels),
    )

    edge_colors <- ifelse(significant, "#000000", "#BEBEBE")

    if (multi_group == TRUE) {
      sem_paths0 <- semPlot::semPaths(fit, layout = layout_algorithm, intercepts = intercepts, what = "paths",
                                      whatLabels = "par", edgeLabels = edgeLabels, residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                      edge.color = edge_colors)

      if (inherits(sem_paths0, 'list')) {
        names(sem_paths0) <- group_info
        sem_paths <- sem_paths0[[1]]
      } else {
        sem_paths <- sem_paths0
      }

    } else {
      if (!is.null(data_file)) {
        sem_paths <- semPlot::semPaths(fit, layout = layout_algorithm, intercepts = intercepts, what = "paths",
                                       whatLabels = "par", edgeLabels = edgeLabels, residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                       edge.color = edge_colors)
      } else {
        sem_paths <- semPlot::semPaths(fit, layout = layout_algorithm, intercepts = intercepts,
                                       what = "paths", whatLabels = "none",
                                       residuals = residuals, plot = FALSE, title = FALSE, DoNotPlot = TRUE,
                                       edge.color = edge_colors)
      }
    }
  }

  return(sem_paths)
}

rotate_around_center <- function(df, rotation_angle) {
  center_x <- mean(df$x)
  center_y <- mean(df$y)
  x <- df$x
  y <- df$y
  angle_rad <- rotation_angle * pi / 180

  x_relative <- x - center_x
  y_relative <- y - center_y

  new_x <- x_relative * cos(angle_rad) - y_relative * sin(angle_rad)
  new_y <- x_relative * sin(angle_rad) + y_relative * cos(angle_rad)

  x <- new_x + center_x
  y <- new_y + center_y

  return(list(x = x, y = y))
}


generate_sem_node_coords <- function(fit, relative_x_position = 25, relative_y_position = 25,
                                     center_x = 0, center_y = 0, flip_layout = FALSE,
                                     flip_direction = NULL, rotate_layout = FALSE, rotate_angle = 0) {

  if (inherits(fit, "sem_graph")) {
    node_names <- fit$nodes$name
    node_coords <- as.data.frame(fit$nodes[,c('x','y','name')])

    if (flip_layout) {
      flipped <- flip_around_center(node_coords, flip_direction)
      node_coords$x <- flipped$x
      node_coords$y <- flipped$y
    }

    if (rotate_layout) {
      rotated <- rotate_around_center(node_coords, rotate_angle)
      node_coords$x <- rotated$x
      node_coords$y <- rotated$y
    }

    # Normalize coordinates to center the graph
    node_coords$x <- (node_coords$x - mean(range(node_coords$x))) * relative_x_position + center_x
    node_coords$y <- (node_coords$y - mean(range(node_coords$y))) * relative_y_position + center_y
    node_coords$name <- node_names

  } else if (inherits(fit, "qgraph")) {
    node_coords <- as.data.frame(fit$layout)
    node_names <- names(fit$graphAttributes$Nodes$labels)
    if (is.null(node_names)) node_names <- fit$graphAttributes$Nodes$labels
    colnames(node_coords) <- c("x", "y")

    if (flip_layout) {
      flipped <- flip_around_center(node_coords, flip_direction)
      node_coords$x <- flipped$x
      node_coords$y <- flipped$y
    }

    if (rotate_layout) {
      rotated <- rotate_around_center(node_coords, rotate_angle)
      node_coords$x <- rotated$x
      node_coords$y <- rotated$y
    }

    node_coords$x <- (node_coords$x - mean(range(node_coords$x))) * relative_x_position + center_x
    node_coords$y <- (node_coords$y - mean(range(node_coords$y))) * relative_y_position + center_y
    node_coords$text <- node_names # name is created later

  } else {
    stop("Must be output from tidySEM with class of 'sem_graph' or semPaths with class of 'qgraph'.")
  }

  return(node_coords)
}

flip_around_center <- function(df, direction) {
  center_x <- mean(df$x)
  center_y <- mean(df$y)
  x <- df$x
  y <- df$y

  if (direction == "horizontal") {
    x <- 2 * center_x - x
  } else if (direction == "vertical") {
    y <- 2 * center_y - y
  } else if (direction == "both") {
    x <- 2 * center_x - x
    y <- 2 * center_y - y
  }

  return(list(x = x, y = y))
}

detect_local_alignment <- function(node_x, node_y, all_x, all_y, tolerance = 0.1) {
  # Find nodes that are horizontally aligned (same Y within tolerance)
  horizontally_aligned <- abs(all_y - node_y) < tolerance
  horizontal_count <- sum(horizontally_aligned)

  # Find nodes that are vertically aligned (same X within tolerance)
  vertically_aligned <- abs(all_x - node_x) < tolerance
  vertical_count <- sum(vertically_aligned)

  # Return the strongest alignment pattern
  if (horizontal_count >= 2 && horizontal_count >= vertical_count) {
    return(list(type = "horizontal", count = horizontal_count, y = node_y))
  } else if (vertical_count >= 2 && vertical_count >= horizontal_count) {
    return(list(type = "vertical", count = vertical_count, x = node_x))
  } else {
    return(list(type = "scattered", count = 1))
  }
}


generate_graph_from_sempaths <- function(fit, node_coords,
                                         latent_shape = "circle", observed_shape = "square",
                                         int_shape = "triangle",
                                         point_size_latent = 20, point_size_observed = 12,
                                         point_size_int = 10,
                                         width_height_ratio_latent = 1,
                                         width_height_ratio_observed = 1,
                                         width_height_ratio_int = 1,
                                         line_width = 1, line_alpha = 1, text_size_latent = 18, text_font_latent = "sans",
                                         text_color_latent = "#FFFFFF", text_alpha_latent = 1, text_fontface_latent = 'plain',
                                         text_size_others = 16, text_font_others = "sans",
                                         text_color_others = "#FFFFFF", text_alpha_others = 1, text_fontface_others = 'plain',
                                         text_size_edges = 14, text_font_edges = "sans",
                                         text_color_edges =  "#000000", text_color_fill = "#FFFFFF", text_alpha_edges = 1, text_fontface_edges = 'plain',
                                         point_color_latent = "#cc3d3d", point_color_observed = "#1262b3",
                                         point_color_int = "#0f993d",
                                         edge_color = "#000000", line_endpoint_spacing = 1.2,
                                         node_border_color = "#FFFFFF",
                                         node_border_width = 1,
                                         arrow_type = "closed", arrow_size = 0.1,
                                         lavaan_arrow_location = "end",
                                         zoom_factor = 1.2,
                                         lavaan_curvature_magnitude = 0.5,
                                         lavaan_rotate_curvature = FALSE,
                                         lavaan_curvature_asymmetry = 0,
                                         lavaan_curved_x_shift = 0,
                                         lavaan_curved_y_shift = 0,
                                         remove_edgelabels = FALSE,
                                         highlight_free_path = FALSE,
                                         ff_params_edge = NULL,
                                         ff_params_edgelabel = NULL,
                                         ff_params_loop = NULL,
                                         ff_params_looplabel = NULL,
                                         highlight_free_path_multi_group = FALSE,
                                         ff_params_edge_multi = NULL,
                                         ff_params_edgelabel_multi = NULL,
                                         ff_params_loop_multi = NULL,
                                         ff_params_looplabel_multi = NULL,
                                         highlight_sig_path = FALSE,
                                         sig_path_color = "#000000",
                                         non_sig_path_color = "#000000",
                                         sig_label_fontface = "plain",
                                         non_sig_label_fontface = "plain",
                                         highlight_multi_group = FALSE,
                                         sig_diff_edge = NULL,
                                         sig_diff_edgelabel = NULL,
                                         sig_diff_loop = NULL,
                                         sig_diff_looplabel = NULL,
                                         residuals = FALSE,
                                         residuals_orientation_type = 'Graded',
                                         lavaan_loop_offset = 0.8,
                                         lavaan_radius = 2.5,
                                         lavaan_line_color_loop = "#000000",
                                         lavaan_line_alpha_loop = 1,
                                         lavaan_arrow_type_loop = "closed",
                                         lavaan_arrow_size_loop = 0.08,
                                         lavaan_width_loop = 1,
                                         lavaan_height_loop = 1,
                                         lavaan_gap_size_loop = 0.05,
                                         lavaan_two_way_arrow_loop = TRUE,
                                         data_file = NULL,
                                         modify_params_edge = FALSE,
                                         modified_edges = NULL,
                                         modify_params_edgelabel = FALSE,
                                         modified_edgelabels = NULL,
                                         modify_params_edgelabel_xy = FALSE,
                                         modified_edgelabels_xy = NULL,
                                         modify_params_edgelabel_text = FALSE,
                                         modified_edgelabels_text = NULL,
                                         modify_params_node = FALSE,
                                         modified_nodes = NULL,
                                         modify_params_node_xy = FALSE,
                                         modified_nodes_xy = NULL,
                                         modify_params_edge_xy = FALSE,
                                         modified_edges_xy = NULL,
                                         modify_params_cov_edge = FALSE,
                                         modified_cov_edges = NULL,
                                         modify_params_nodelabel = FALSE,
                                         modified_nodelabels = NULL,
                                         modify_params_nodelabel_xy = FALSE,
                                         modified_nodelabels_xy = NULL,
                                         modify_params_nodelabel_text = FALSE,
                                         modified_nodelabels_text = NULL,
                                         modify_params_latent_node_xy = FALSE,
                                         modified_latent_nodes_xy = NULL,
                                         modify_params_latent_node_angle = FALSE,
                                         modified_latent_nodes_angle = NULL,
                                         modify_params_loop = FALSE,
                                         modified_loops = NULL,
                                         modify_params_loop_xy = FALSE,
                                         modified_loops_xy = NULL,
                                         modify_params_loop_location = FALSE,
                                         modified_loops_location = NULL,
                                         modify_params_looplabel = FALSE,
                                         modified_looplabels = NULL,
                                         modify_params_looplabel_xy = FALSE,
                                         modified_looplabels_xy = NULL,
                                         modify_params_looplabel_text = FALSE,
                                         modified_looplabels_text = NULL,
                                         loop_names_remove = NULL,
                                         which_group = "1") {

  apply_modifications <- function(data, modifications, config, mode, batch_process = FALSE) {
    if (is.null(modifications) || nrow(modifications) == 0) return(data)
    modified_data <- data

    if (batch_process && !is.null(config$batch_special_case)) {
      mods <- modifications
      modified_data <- config$batch_special_case(modified_data, mods)
    } else {

      for (i in seq_len(nrow(modifications))) {
        mod <- modifications[i, ]

        if (mode == 'edge') {
          idx <- which(
            edges_from == mod$lhs &
              edges_to == mod$rhs
          )

        } else if (mode == 'node') {
          idx <- which(
            node_coords$name == mod$text
          )
        } else if (mode == 'loop') {
          idx <- which(
            node_coords$name[!node_coords$name %in% loop_names_remove] == mod$text
          )
        }

        if (length(idx) > 0) {
          for (col in config$modify_cols) {
            if (col %in% names(mod) && col %in% names(modified_data)) {
              modified_data[idx, col] <- mod[[col]]
            }
          }

          if (!is.null(config$special_case)) {
            modified_data <- config$special_case(modified_data, idx, mod)
          }
        }
      }
    }
    return(modified_data)
  }

  if (inherits(fit, "qgraph")) {
    sem_paths <- fit
    node_names <- names(sem_paths$graphAttributes$Nodes$labels)
    if (is.null(node_names)) node_names <- sem_paths$graphAttributes$Nodes$labels
    node_types <- sem_paths$graphAttributes$Nodes$shape

    # Node classification
    latent_vars <- node_names[node_types == "circle"]
    observed_vars <- node_names[node_types == "square"]
    intercept_vars <- node_names[node_types == "triangle"]

    # Process edges
    edges_df0 <- data.frame(
      from = sem_paths$Edgelist$from,
      to = sem_paths$Edgelist$to,
      weight = sem_paths$Edgelist$weight,
      directed = sem_paths$Edgelist$directed,
      bidirectional = sem_paths$Edgelist$bidirectional,
      labels = sem_paths$graphAttributes$Edges$labels,
      sig = ifelse(sem_paths$graphAttributes$Edge$color == "#000000FF", TRUE, FALSE)
    )

    self_loop_indices <- which(edges_df0$from == edges_df0$to)

    if (length(self_loop_indices) > 0) {
      edges_df0$self_loop <- FALSE
      edges_df0$self_loop[self_loop_indices] <- TRUE
    } else {
      edges_df0$self_loop <- FALSE
    }

    edges_df0 <- edges_df0[!duplicated(
      t(apply(edges_df0[c("from", "to")], 1, sort))
    ), ]

    edges_df <- edges_df0[!edges_df0$self_loop, ]
    edges_loop_df <- edges_df0[edges_df0$self_loop,]
    loop_node_names <- node_names[edges_loop_df$from]

    # Handle intercepts
    intercept_indices <- which(node_names == "1")
    intercept_sources <- character(length(intercept_indices))

    for (i in seq_along(intercept_indices)) {
      intercept_idx <- intercept_indices[i]

      connected_edges <- edges_df[edges_df$from == intercept_idx | edges_df$to == intercept_idx, ]

      if (nrow(connected_edges) > 0) {
        target_nodes <- c(connected_edges$from, connected_edges$to)
        target_nodes <- target_nodes[target_nodes != intercept_idx]

        if (length(target_nodes) > 0) {
          target_var <- node_names[target_nodes[1]]
          intercept_sources[i] <- paste0("Intercept_", target_var)
        } else {
          intercept_sources[i] <- paste0("Intercept_", i)
        }
      } else {
        intercept_sources[i] <- paste0("Intercept_", i)
      }
    }

    node_names[intercept_indices] <- intercept_sources

    edges_from <- node_names[edges_df$from]
    edges_to <- node_names[edges_df$to]

    # Edge properties
    edge_op <- ifelse(edges_df$bidirectional, "~~", "~")
    edge_labels <- edges_df$labels
    edge_sig <- edges_df$sig

    # Node properties
    node_shapes <- ifelse(node_names %in% intercept_sources, int_shape,
                          ifelse(node_names %in% latent_vars, latent_shape, observed_shape))
    node_colors <- ifelse(node_names %in% intercept_sources, point_color_int,
                          ifelse(node_names %in% latent_vars, point_color_latent, point_color_observed))
    node_sizes <- ifelse(node_names %in% intercept_sources, point_size_int,
                         ifelse(node_names %in% latent_vars, point_size_latent, point_size_observed))
    node_width_height_ratios <- ifelse(node_names %in% intercept_sources, width_height_ratio_int,
                                       ifelse(node_names %in% latent_vars, width_height_ratio_latent, width_height_ratio_observed))

    node_coords$name <- node_names

  } else {

    stop("Must be output from semPaths with class of 'qgraph'.")

  }


  if (modify_params_latent_node_angle) {
    node_coords <- apply_modifications(
      node_coords,
      modified_latent_nodes_angle,
      config = list(
        batch_special_case = function(data, mods) {

          latent_positions <- which(mods$node_type == "latent")

          most_recent_groups <- list()

          for (i in seq_along(latent_positions)) {
            current_latent_pos <- latent_positions[i]

            if (i == length(latent_positions) ||
                (current_latent_pos + 1 < latent_positions[i + 1] &&
                 (mods$node_type[current_latent_pos + 1] == "observed" ||
                  mods$node_type[current_latent_pos + 1] == "intercept"))) {

              group_rows <- current_latent_pos
              next_row <- current_latent_pos + 1

              # Continue while next rows are observed OR intercept nodes
              while (next_row <= nrow(mods) &&
                     (mods$node_type[next_row] == "observed" ||
                      mods$node_type[next_row] == "intercept")) {
                group_rows <- c(group_rows, next_row)
                next_row <- next_row + 1
              }

              group_data <- mods[group_rows, ]
              latent_name <- group_data$text[1]

              if (is.null(most_recent_groups[[latent_name]]) ||
                  group_rows[1] > most_recent_groups[[latent_name]]$positions[1]) {
                most_recent_groups[[latent_name]] <- list(
                  data = group_data,
                  positions = group_rows
                )
              }
            }
          }

          final_mods_list <- lapply(most_recent_groups, function(x) x$data)

          for (i in seq_along(final_mods_list)) {
            curr_mods <- final_mods_list[[i]]
            latent_mod <- curr_mods[curr_mods$node_type == "latent", ]
            observed_mods <- curr_mods[curr_mods$node_type == "observed", ]
            intercept_mods <- curr_mods[curr_mods$node_type == "intercept", ]

            latent_node_idx <- which(data$name == latent_mod$text) # idx in node_coords
            obs_node_idx <- which(data$name %in% observed_mods$text)
            int_node_idx <- which(data$name %in% intercept_mods$text)

            # Combine observed and intercept nodes for rotation
            all_child_nodes <- c(obs_node_idx, int_node_idx)

            if (length(latent_node_idx) > 0 && length(all_child_nodes) > 0) {
              # Calculate centroid (latent node xy)
              center_x <- data$x[latent_node_idx]
              center_y <- data$y[latent_node_idx]

              angle_rad <- curr_mods$angle * pi / 180

              for (child_idx in all_child_nodes) {
                if(length(child_idx) > 0) {
                  x_relative <- data$x[child_idx] - center_x
                  y_relative <- data$y[child_idx] - center_y

                  new_x <- x_relative * cos(angle_rad) - y_relative * sin(angle_rad)
                  new_y <- x_relative * sin(angle_rad) + y_relative * cos(angle_rad)

                  data$x[child_idx] <- new_x + center_x
                  data$y[child_idx] <- new_y + center_y
                }
              }
            }
          }

          return(data)
        }
      ),
      mode = 'node', # ignored
      batch_process = TRUE
    )
  }

  # Apply node position modifications
  if (modify_params_node_xy) {
    node_coords <- apply_modifications(
      node_coords,
      modified_nodes_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0), # No direct column mods
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }

  # Apply latent node group position modifications
  if (modify_params_latent_node_xy) {
    node_coords <- apply_modifications(
      node_coords,
      modified_latent_nodes_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0), # No direct column mods
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }

  # Create points dataframe
  points_df <- data.frame(
    x = node_coords$x,
    y = node_coords$y,
    shape = node_shapes,
    color = node_colors,
    size = node_sizes,
    border_color = node_border_color,
    border_width = node_border_width,
    alpha = 1,
    width_height_ratio = node_width_height_ratios,
    orientation = 0,
    lavaan = TRUE,
    network = FALSE,
    locked = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  #Apply node modifications
  if (modify_params_node) {
    points_df <- apply_modifications(
      points_df,
      modified_nodes,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "alpha", "shape", "size", "border_color", "border_width", "width_height_ratio"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }

  # Create annotations
  annotations <- data.frame(
    text = node_coords$text,
    x = node_coords$x,
    y = node_coords$y,
    font = ifelse(node_names %in% latent_vars, text_font_latent, text_font_others),
    size = ifelse(node_names %in% latent_vars, text_size_latent, text_size_others),
    color = ifelse(node_names %in% latent_vars, text_color_latent, text_color_others),
    fill = NA,
    angle = 0,
    alpha = ifelse(node_names %in% latent_vars, text_alpha_latent, text_alpha_others),
    fontface = ifelse(node_names %in% latent_vars, text_fontface_latent, text_fontface_others),
    math_expression = FALSE,
    hjust = 0.5,
    vjust = 0.5,
    lavaan = TRUE,
    network = FALSE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )


  if (modify_params_nodelabel) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = c("color", "size", "alpha", "angle", "font", "fontface"),
        special_case = NULL
      ),
      mode = 'node'
    )
  }


  if (modify_params_nodelabel_xy) {
    annotations <- apply_modifications(
      annotations,
      modified_nodelabels_xy,
      config = list(
        match_cols = c(name = "text"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'node'
    )
  }


  if (modify_params_nodelabel_text) {
    if (nrow(modified_nodelabels_text) > 0) {
      for (i in seq_len(nrow(modified_nodelabels_text))) {
        mod <- modified_nodelabels_text[i, ]
        node_idx <- which(
          node_coords$name == mod$text
        )
        if (length(node_idx) == 1) {
          annotations$text[[node_idx]] <- mod$nodelabel
          annotations$math_expression[[node_idx]] <- mod$math_expression
        }
      }
    }
  }

  if (length(edges_from) == 0 || length(edges_to) == 0) {
    stop("No edges found in the model. Check the Lavaan syntax.")
  }

  # Create lines dataframe
  lines_df_pre <- data.frame(
    x_start = node_coords[match(edges_from, node_names), "x"],
    y_start = node_coords[match(edges_from, node_names), "y"],
    x_end = node_coords[match(edges_to, node_names), "x"],
    y_end = node_coords[match(edges_to, node_names), "y"],
    ctrl_x = NA,
    ctrl_y = NA,
    ctrl_x2 = NA,
    ctrl_y2 = NA,
    curvature_magnitude = NA,
    rotate_curvature = NA,
    curvature_asymmetry = NA,
    type = ifelse(edge_op == "~~", "Curved Arrow", "Straight Arrow"),
    color = edge_color,
    end_color = NA,
    color_type = "Single",
    gradient_position = NA,
    width = line_width,
    alpha = line_alpha,
    arrow = TRUE,
    arrow_type = arrow_type,
    arrow_size = arrow_size,
    two_way = edge_op == "~~",
    lavaan = TRUE,
    network = FALSE,
    line_style = "solid",
    locked = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )


  # Apply edge modifications

  if (highlight_free_path) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      ff_params_edge,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color")
      ),
      mode = 'edge'
    )
  }

  # Highlight significant paths if requested
  edge_sig_idx <- which(edges_df$sig == TRUE)
  non_edge_sig_idx <- which(edges_df$sig == FALSE)

  if (highlight_sig_path) {
    lines_df_pre$color[edge_sig_idx] <- sig_path_color
    lines_df_pre$color[non_edge_sig_idx] <- non_sig_path_color
  }

  if (highlight_free_path_multi_group) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      ff_params_edge_multi,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "width")
      ),
      mode = 'edge'
    )
  }

  if (highlight_multi_group) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      sig_diff_edge,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "width")
      ),
      mode = 'edge'
    )
  }

  if (modify_params_edge) {
    lines_df_pre <- apply_modifications(
      lines_df_pre,
      modified_edges,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "width", "alpha", "line_style", "end_color", "gradient_position", "color_type")
      ),
      mode = 'edge'
    )
  }

  # Adjust edge coordinates
  edge_list <- cbind(match(edges_from, node_names), match(edges_to, node_names))

  lines_df <- adjust_edge_coordinates(
    lines_df = lines_df_pre,
    edge_list = edge_list,
    points_df = points_df,
    auto_endpoint_spacing = line_endpoint_spacing
  )

  if ("two_way" %in% colnames(lines_df) && any(lines_df$two_way, na.rm = TRUE)) {
    lines_df[lines_df$two_way, c("x_start", "x_end", "y_start", "y_end")] <-
      lines_df[lines_df$two_way, c("x_start", "x_end", "y_start", "y_end")] +
      c(lavaan_curved_x_shift, lavaan_curved_x_shift, lavaan_curved_y_shift, lavaan_curved_y_shift)
  }

  if (any(lines_df$two_way)) {
    two_way_indices <- which(lines_df$two_way)
    control_points <- mapply(
      calculate_control_point,
      x_start = lines_df$x_start[two_way_indices],
      y_start = lines_df$y_start[two_way_indices],
      x_end = lines_df$x_end[two_way_indices],
      y_end = lines_df$y_end[two_way_indices],
      curvature_magnitude = lavaan_curvature_magnitude,
      rotate_curvature = lavaan_rotate_curvature,
      curvature_asymmetry = lavaan_curvature_asymmetry,
      center_x = mean(node_coords$x),
      center_y = mean(node_coords$y),
      SIMPLIFY = FALSE
    )

    lines_df$ctrl_x[two_way_indices] <- sapply(control_points, `[[`, "ctrl_x")
    lines_df$ctrl_y[two_way_indices] <- sapply(control_points, `[[`, "ctrl_y")
    lines_df$ctrl_x2[two_way_indices] <- sapply(control_points, `[[`, "ctrl_x2")
    lines_df$ctrl_y2[two_way_indices] <- sapply(control_points, `[[`, "ctrl_y2")

    lines_df$curvature_magnitude[two_way_indices] <- lavaan_curvature_magnitude
    lines_df$rotate_curvature[two_way_indices] <- lavaan_rotate_curvature
    lines_df$curvature_asymmetry[two_way_indices] <- lavaan_curvature_asymmetry
  }

  # Apply covariance edge modifications
  if (modify_params_cov_edge) {
    lines_df <- apply_modifications(
      lines_df,
      modified_cov_edges,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = NULL,
        special_case = function(data, idx, mod) {
          # Add input validation
          if (length(idx) == 1 &&
              all(c("x_start", "y_start", "x_end", "y_end") %in% names(data)) &&
              all(c("curvature_magnitude", "rotate_curvature", "curvature_asymmetry", "x_shift", "y_shift") %in% names(mod))) {

            data$x_start[idx] <- data$x_start[idx] + mod$x_shift
            data$x_end[idx] <- data$x_end[idx] + mod$x_shift
            data$y_start[idx] <- data$y_start[idx] + mod$y_shift
            data$y_end[idx] <- data$y_end[idx] + mod$y_shift

            cp <- calculate_control_point(
              x_start = data$x_start[idx],
              y_start = data$y_start[idx],
              x_end = data$x_end[idx],
              y_end = data$y_end[idx],
              curvature_magnitude = mod$curvature_magnitude,
              rotate_curvature = mod$rotate_curvature,
              curvature_asymmetry = mod$curvature_asymmetry,
              center_x = mean(node_coords$x),
              center_y = mean(node_coords$y)
            )

            # Safely assign control points
            if (all(c("ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2", "locked") %in% names(data))) {
              data$ctrl_x[idx] <- cp$ctrl_x
              data$ctrl_y[idx] <- cp$ctrl_y
              data$ctrl_x2[idx] <- cp$ctrl_x2
              data$ctrl_y2[idx] <- cp$ctrl_y2
              data$curvature_magnitude[idx] <- mod$curvature_magnitude
              data$rotate_curvature[idx] <- mod$rotate_curvature
              data$curvature_asymmetry[idx] <- mod$curvature_asymmetry
              data$locked[idx] <- FALSE
            }
          }
          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  lines_df$type[lines_df$curvature_magnitude == 0] <- "Straight Arrow"
  lines_df$type[lines_df$curvature_magnitude != 0] <- "Curved Arrow"

  if (modify_params_edge_xy) {
    lines_df <- apply_modifications(
      lines_df,
      modified_edges_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x_start[idx] <- mod$start_x_shift # data$x_start[idx] + mod$start_x_shift
          data$y_start[idx] <- mod$start_y_shift # data$y_start[idx] + mod$start_y_shift
          data$x_end[idx] <- mod$end_x_shift # data$x_end[idx] + mod$end_x_shift
          data$y_end[idx] <- mod$end_y_shift # data$y_end[idx] + mod$end_y_shift

          if (data$type[idx] %in% c('Curved Line', 'Curved Arrow')) {
            cp <- calculate_control_point(
              x_start = data$x_start[idx],
              y_start = data$y_start[idx],
              x_end = data$x_end[idx],
              y_end = data$y_end[idx],
              curvature_magnitude = data$curvature_magnitude[idx],
              rotate_curvature = data$rotate_curvature[idx],
              curvature_asymmetry = data$curvature_asymmetry[idx],
              center_x = mean(node_coords$x),
              center_y = mean(node_coords$y)
            )

            data$ctrl_x[idx] <- cp$ctrl_x
            data$ctrl_y[idx] <- cp$ctrl_y
            data$ctrl_x2[idx] <- cp$ctrl_x2
            data$ctrl_y2[idx] <- cp$ctrl_y2
          }

          return(data)
        }
      ),
      mode = 'edge'
    )
  }


  # Handle arrow location
  if (exists("lavaan_arrow_location") && lavaan_arrow_location == "start") {
    temp <- lines_df[, c("x_start", "y_start")]
    lines_df[, c("x_start", "y_start")] <- lines_df[, c("x_end", "y_end")]
    lines_df[, c("x_end", "y_end")] <- temp
  }

  # Prepare self-loop arrows

  if (!is.null(loop_names_remove)) {
    loop_node_names <- loop_node_names[!loop_node_names %in% loop_names_remove]
  }


  if (residuals) {
    loops_df = data.frame(
      x_center = node_coords[match(loop_node_names, node_names), "x"],
      y_center = node_coords[match(loop_node_names, node_names), "y"],
      radius = lavaan_radius,
      color = lavaan_line_color_loop,
      width = lavaan_width_loop,
      alpha = lavaan_line_alpha_loop,
      arrow_type = lavaan_arrow_type_loop,
      arrow_size = lavaan_arrow_size_loop,
      gap_size = lavaan_gap_size_loop,
      loop_width = lavaan_width_loop,
      loop_height = lavaan_height_loop,
      orientation = 0, # later modified
      lavaan = TRUE,
      two_way = lavaan_two_way_arrow_loop,
      locked = FALSE,
      group = which_group,
      stringsAsFactors = FALSE
    )

    offset_distance <- lavaan_loop_offset

    for (i in 1:nrow(loops_df)) {
      node_name <- loop_node_names[i]
      node_index <- which(node_names == node_name)

      node_x <- points_df$x[node_index]
      node_y <- points_df$y[node_index]
      node_shape <- points_df$shape[node_index]
      node_size <- points_df$size[node_index]
      node_ratio <- points_df$width_height_ratio[node_index]
      node_orientation <- points_df$orientation[node_index]

      connected_edges <- lines_df[lines_df$edges_from == node_name | lines_df$edges_to == node_name, ]

      if (nrow(connected_edges) > 0) {
        edge_vectors <- list()

        for (j in 1:nrow(connected_edges)) {
          if (connected_edges$edges_from[j] == node_name) {
            # Outgoing edge
            dx <- connected_edges$x_end[j] - connected_edges$x_start[j]
            dy <- connected_edges$y_end[j] - connected_edges$y_start[j]
          } else {
            # Incoming edge (reverse direction)
            dx <- connected_edges$x_start[j] - connected_edges$x_end[j]
            dy <- connected_edges$y_start[j] - connected_edges$y_end[j]
          }
          edge_vectors[[j]] <- c(dx, dy)
        }

        avg_dx <- mean(sapply(edge_vectors, function(v) v[1]))
        avg_dy <- mean(sapply(edge_vectors, function(v) v[2]))

        angle_to_connections <- atan2(avg_dy, avg_dx) * 180 / pi
        gap_angle <- (angle_to_connections + 180) %% 360  # Opposite direction

      } else {
        dx_to_center <- mean(node_coords$x) - node_x
        dy_to_center <- mean(node_coords$y) - node_y
        gap_angle <- atan2(dy_to_center, dx_to_center) * 180 / pi
        gap_angle <- ifelse(gap_angle < 0, gap_angle + 360, gap_angle)
      }

      if (residuals_orientation_type == 'Quadratic') {
        quadrant_angles <- c(0, 90, 180, 270)
        angle_differences <- abs(gap_angle - quadrant_angles)
        angle_differences <- pmin(angle_differences, 360 - angle_differences)
        nearest_quadrant <- quadrant_angles[which.min(angle_differences)]
        final_gap_angle <- nearest_quadrant
      } else {
        final_gap_angle <- gap_angle
      }

      loops_df$orientation[i] <- (final_gap_angle - 90) %% 360

      alignment <- detect_local_alignment(node_x, node_y, node_coords$x, node_coords$y)

      if (alignment$type == "horizontal" && alignment$count >= 3) {
        group_dx <- 0
        group_dy <- ifelse(node_y > mean(node_coords$y), 1, -1)  # Outer side
      } else if (alignment$type == "vertical" && alignment$count >= 3) {
        group_dx <- ifelse(node_x > mean(node_coords$x), 1, -1)  # Outer side
        group_dy <- 0
      } else {
        position_angle <- (final_gap_angle + 180) %% 360
        group_dx <- cos(position_angle * pi / 180)
        group_dy <- sin(position_angle * pi / 180)
      }

      boundary_point <- find_intersection(
        x_center = node_x,
        y_center = node_y,
        x_target = node_x + group_dx,
        y_target = node_y + group_dy,
        size = node_size,
        width_height_ratio = node_ratio,
        orientation = node_orientation,
        shape = node_shape
      )

      dx_boundary <- boundary_point$x - node_x
      dy_boundary <- boundary_point$y - node_y
      distance_to_boundary <- sqrt(dx_boundary^2 + dy_boundary^2)

      if (distance_to_boundary > 0) {
        scale <- (distance_to_boundary + offset_distance) / distance_to_boundary
        loops_df$x_center[i] <- node_x + dx_boundary * scale
        loops_df$y_center[i] <- node_y + dy_boundary * scale
      } else {
        loops_df$x_center[i] <- node_x + group_dx * offset_distance
        loops_df$y_center[i] <- node_y + group_dy * offset_distance
      }

      loop_x <- loops_df$x_center[i]
      loop_y <- loops_df$y_center[i]

      dx_loop_to_node <- node_x - loop_x
      dy_loop_to_node <- node_y - loop_y

      angle_loop_to_node <- atan2(dy_loop_to_node, dx_loop_to_node) * 180 / pi
      angle_loop_to_node <- ifelse(angle_loop_to_node < 0, angle_loop_to_node + 360, angle_loop_to_node)

      loops_df$orientation[i] <- (angle_loop_to_node - 90) %% 360
    }

  } else {
    loops_df = data.frame(
      x_center = numeric(), y_center = numeric(), radius = numeric(), color = character(),
      width = numeric(), alpha = numeric(), arrow_type = character(), arrow_size = numeric(),
      gap_size = numeric(), loop_width = numeric(), loop_height = numeric(), orientation = numeric(),
      lavaan = logical(), two_way = logical(), locked = logical(), group = character(), stringsAsFactors = FALSE
    )
  }

  if (residuals) {

    if (highlight_free_path) {
      loops_df <- apply_modifications(
        loops_df,
        ff_params_loop,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color")
        ),
        mode = 'loop'
      )
    }

    loop_sig_idx <- which(edges_loop_df$sig == TRUE)
    non_loop_sig_idx <- which(edges_loop_df$sig == FALSE)

    if (highlight_sig_path) {
      loops_df$color[loop_sig_idx] <- sig_path_color
      loops_df$color[non_loop_sig_idx] <- non_sig_path_color
    }

    if (highlight_free_path_multi_group) {
      loops_df <- apply_modifications(
        loops_df,
        ff_params_loop_multi,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "width", "radius")
        ),
        mode = 'loop'
      )
    }

    if (highlight_multi_group) {
      loops_df <- apply_modifications(
        loops_df,
        sig_diff_loop,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "width"),
          special_case = NULL
        ),
        mode = 'loop'
      )
    }

    if (modify_params_loop) {
      loops_df <- apply_modifications(
        loops_df,
        modified_loops,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "alpha", "radius", "width", "type", "arrow_size", "gap_size", "two_way"),
          special_case = NULL
        ),
        mode = 'loop'
      )
    }

    if (modify_params_loop_xy) {
      loops_df <- apply_modifications(
        loops_df,
        modified_loops_xy,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = character(0), # No direct column mods
          special_case = function(data, idx, mod) {
            data$x_center[idx] <- data$x_center[idx] + mod$x_shift
            data$y_center[idx] <- data$y_center[idx] + mod$y_shift
            return(data)
          }
        ),
        mode = 'loop'
      )
    }

    if (modify_params_loop_location) {
      loops_df <- apply_modifications(
        loops_df,
        modified_loops_location,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = character(0),
          special_case = function(data, idx, mod) {
            # For each modified loop, recompute position and orientation
            for (i in idx) {
              node_name <- loop_node_names[i]
              node_index <- which(node_names == node_name)

              node_x <- points_df$x[node_index]
              node_y <- points_df$y[node_index]
              node_shape <- points_df$shape[node_index]
              node_size <- points_df$size[node_index]
              node_ratio <- points_df$width_height_ratio[node_index]
              node_orientation <- points_df$orientation[node_index]

              loop_angle <- as.numeric(mod$loop_location)

              angle_rad <- loop_angle * pi / 180
              group_dx <- cos(angle_rad)
              group_dy <- sin(angle_rad)

              # Find boundary point and apply offset
              boundary_point <- find_intersection(
                x_center = node_x,
                y_center = node_y,
                x_target = node_x + group_dx,
                y_target = node_y + group_dy,
                size = node_size,
                width_height_ratio = node_ratio,
                orientation = node_orientation,
                shape = node_shape
              )

              offset_distance <- lavaan_loop_offset

              dx_boundary <- boundary_point$x - node_x
              dy_boundary <- boundary_point$y - node_y
              distance_to_boundary <- sqrt(dx_boundary^2 + dy_boundary^2)

              # Update loop position
              if (distance_to_boundary > 0) {
                scale <- (distance_to_boundary + offset_distance) / distance_to_boundary
                data$x_center[i] <- node_x + dx_boundary * scale
                data$y_center[i] <- node_y + dy_boundary * scale
              } else {
                data$x_center[i] <- node_x + group_dx * offset_distance
                data$y_center[i] <- node_y + group_dy * offset_distance
              }

              # Recalculate orientation based on new position
              loop_x <- data$x_center[i]
              loop_y <- data$y_center[i]

              dx_loop_to_node <- node_x - loop_x
              dy_loop_to_node <- node_y - loop_y

              angle_loop_to_node <- atan2(dy_loop_to_node, dx_loop_to_node) * 180 / pi
              angle_loop_to_node <- ifelse(angle_loop_to_node < 0, angle_loop_to_node + 360, angle_loop_to_node)

              data$orientation[i] <- (angle_loop_to_node - 90) %% 360
            }

            return(data)
          }
        ),
        mode = 'loop'
      )
    }
  }

  # Prepare edge labels
  lines_df0 <- cbind(lines_df, from = edges_from, to = edges_to, text = edge_labels)
  edgelabels_xy_df <- data.frame(x = numeric(nrow(lines_df0)), y = numeric(nrow(lines_df0)))

  for (i in seq_len(nrow(lines_df0))) {
    intp_points <- if (lines_df0$type[i] == "Curved Arrow") {
      create_bezier_curve(
        x_start = lines_df0$x_start[i], y_start = lines_df0$y_start[i],
        x_end = lines_df0$x_end[i], y_end = lines_df0$y_end[i],
        ctrl_x = lines_df0$ctrl_x[i], ctrl_y = lines_df0$ctrl_y[i],
        ctrl_x2 = lines_df0$ctrl_x2[i], ctrl_y2 = lines_df0$ctrl_y2[i], n_points = 100
      )
    } else {
      interpolate_points(
        x_start = lines_df0$x_start[i], y_start = lines_df0$y_start[i],
        x_end = lines_df0$x_end[i], y_end = lines_df0$y_end[i], n = 100
      )
    }

    mid_idx <- ifelse(lines_df0$type[i] == "Curved Arrow",
                      find_peak_point(
                        intp_points,
                        x_start = lines_df0$x_start[i],
                        y_start = lines_df0$y_start[i],
                        x_end = lines_df0$x_end[i],
                        y_end = lines_df0$y_end[i]
                      ),
                      50)
    #mid_idx <- 50
    edgelabels_xy_df[i, ] <- intp_points[mid_idx, c("x", "y")]
  }

  pval_idx <- grep("\\*", lines_df0$text)
  label_coords <- data.frame(
    text = as.character(lines_df0$text),
    x = edgelabels_xy_df$x,
    y = edgelabels_xy_df$y,
    font = text_font_edges,
    size = text_size_edges,
    color = text_color_edges,
    fill = ifelse(lines_df0$text == "", NA, text_color_fill),
    angle = 0,
    alpha = text_alpha_edges,
    fontface = text_fontface_edges,
    math_expression = FALSE,
    hjust = 0.5,
    vjust = 0.5,
    lavaan = TRUE,
    network = FALSE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  ) |>
    filter(nzchar(trimws(text)))

  edgelabels_sig_idx <- which(edges_df$sig == TRUE)
  non_edgelabels_sig_idx <- which(edges_df$sig == FALSE)

  if (highlight_free_path) {
    label_coords <- apply_modifications(
      label_coords,
      ff_params_edgelabel,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (highlight_sig_path) {
    label_coords$fontface[edgelabels_sig_idx] <- sig_label_fontface
    label_coords$fontface[non_edgelabels_sig_idx] <- non_sig_label_fontface
    label_coords$color[edgelabels_sig_idx] <- sig_path_color
    label_coords$color[non_edgelabels_sig_idx] <- non_sig_path_color
  }

  if (highlight_free_path_multi_group) {
    label_coords <- apply_modifications(
      label_coords,
      ff_params_edgelabel_multi,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (highlight_multi_group) {
    label_coords <- apply_modifications(
      label_coords,
      sig_diff_edgelabel,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fontface")
      ),
      mode = 'edge'
    )
  }
  # Apply edge label modifications

  if (modify_params_edgelabel) {
    label_coords <- apply_modifications(
      label_coords,
      modified_edgelabels,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = c("color", "fill", "size", "alpha", "angle", "font", "fontface")
      ),
      mode = 'edge'
    )
  }

  if (modify_params_edgelabel_xy) {
    label_coords <- apply_modifications(
      label_coords,
      modified_edgelabels_xy,
      config = list(
        match_cols = c(from = "lhs", to = "rhs"),
        modify_cols = character(0),
        special_case = function(data, idx, mod) {
          data$x[idx] <- data$x[idx] + mod$x_shift
          data$y[idx] <- data$y[idx] + mod$y_shift
          return(data)
        }
      ),
      mode = 'edge'
    )
  }

  if (modify_params_edgelabel_text) {
    if (nrow(modified_edgelabels_text) > 0) {
      for (i in seq_len(nrow(modified_edgelabels_text))) {
        mod <- modified_edgelabels_text[i, ]
        edge_idx <- which(
          edges_from == mod$lhs &
            edges_to == mod$rhs
        )
        if (length(edge_idx) == 1) {
          label_coords$text[[edge_idx]] <- mod$text
          label_coords$math_expression[[edge_idx]] <- mod$math_expression
        }
      }
    }
  }


  if (residuals) {
    # Prepare loop labels
    loop_labels_df <- data.frame(
      x = numeric(nrow(loops_df)),
      y = numeric(nrow(loops_df)),
      text = character(nrow(loops_df)),
      stringsAsFactors = FALSE
    )

    for (i in 1:nrow(loops_df)) {
      node_name <- loop_node_names[i]
      node_index <- which(node_names == node_name)

      loop_label_row <- edges_loop_df[edges_loop_df$from == node_index & edges_loop_df$self_loop == TRUE, ]

      if (nrow(loop_label_row) > 0) {
        loop_labels_df$text[i] <- as.character(loop_label_row$labels[1])
      } else {
        loop_labels_df$text[i] <- ""
      }

      node_x <- loops_df$x_center[i]  # Current loop center (after positioning)
      node_y <- loops_df$y_center[i]
      loop_radius <- loops_df$radius[i]
      loop_orientation <- loops_df$orientation[i]

      gap_angle <- (loop_orientation + 90) %% 360
      label_angle <- (gap_angle + 180) %% 360  # Opposite side

      label_angle_rad <- label_angle * pi / 180

      loop_labels_df$x[i] <- node_x + loop_radius * cos(label_angle_rad)
      loop_labels_df$y[i] <- node_y + loop_radius * sin(label_angle_rad)
    }

    loop_label_coords <- data.frame(
      text = loop_labels_df$text,
      x = loop_labels_df$x,
      y = loop_labels_df$y,
      font = text_font_edges,
      size = text_size_edges,
      color = text_color_edges,
      fill = ifelse(loop_labels_df$text == "", NA, text_color_fill),
      angle = 0,
      alpha = text_alpha_edges,
      fontface = text_fontface_edges,
      math_expression = FALSE,
      hjust = 0.5,
      vjust = 0.5,
      lavaan = TRUE,
      network = FALSE,
      locked = FALSE,
      group_label = FALSE,
      loop_label = TRUE,
      group = which_group,
      stringsAsFactors = FALSE
    ) |>
      filter(nzchar(trimws(text)))
  } else {
    loop_label_coords <- data.frame(
      text = character(), x = numeric(), y = numeric(), font = character(), size = numeric(), color = character(), fill = character(), angle = numeric(), alpha = numeric(),
      fontface = character(), math_expression = logical(), hjust = numeric(), vjust = numeric(), lavaan = logical(), network = logical(), locked = logical(), group_label = logical(), loop_label = logical(), group = character(),
      stringsAsFactors = FALSE
    )
  }

  if (residuals) {

    if (highlight_free_path) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        ff_params_looplabel,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "fontface")
        ),
        mode = 'loop'
      )
    }

    if (highlight_sig_path) {
      loop_label_coords$fontface[loop_sig_idx] <- sig_label_fontface
      loop_label_coords$fontface[loop_sig_idx] <- non_sig_label_fontface
      loop_label_coords$color[loop_sig_idx] <- sig_path_color
      loop_label_coords$color[non_loop_sig_idx] <- non_sig_path_color
    }

    if (highlight_free_path_multi_group) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        ff_params_looplabel_multi,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "fontface")
        ),
        mode = 'loop'
      )
    }

    if (highlight_multi_group) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        sig_diff_looplabel,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "fontface")
        ),
        mode = 'loop'
      )
    }

    if (modify_params_looplabel) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        modified_looplabels,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = c("color", "fill", "size", "alpha", "angle", "font", "fontface"),
          special_case = NULL
        ),
        mode = 'loop'
      )
    }

    if (modify_params_looplabel_xy) {
      loop_label_coords <- apply_modifications(
        loop_label_coords,
        modified_looplabels_xy,
        config = list(
          match_cols = c(name = "text"),
          modify_cols = character(0),
          special_case = function(data, idx, mod) {
            data$x[idx] <- data$x[idx] + mod$x_shift
            data$y[idx] <- data$y[idx] + mod$y_shift
            return(data)
          }
        ),
        mode = 'loop'
      )
    }

    if (modify_params_looplabel_text) {
      if (nrow(modified_looplabels_text) > 0) {
        for (i in seq_len(nrow(modified_looplabels_text))) {
          mod <- modified_looplabels_text[i, ]
          loop_idx <- which(
            node_coords$name[!node_coords$name %in% loop_names_remove] == mod$text
          )
          if (length(loop_idx) == 1) {
            loop_label_coords$text[[loop_idx]] <- mod$looplabel
            loop_label_coords$math_expression[[loop_idx]] <- mod$math_expression
          }
        }
      }
    }
  }

  if (remove_edgelabels) {
    label_coords <- data.frame(
      text = character(),
      x = numeric(),
      y = numeric(),
      font = character(),
      size = numeric(),
      color = character(),
      fill = character(),
      angle = numeric(),
      alpha = numeric(),
      fontface = character(),
      math_expression = logical(),
      hjust = numeric(),
      vjust = numeric(),
      lavaan = logical(),
      network = logical(),
      locked = logical(),
      group_label = logical(),
      loop_label = logical(),
      group = character(),
      stringsAsFactors = FALSE
    )
  }

  if (!is.null(data_file)) {
    if (data_file) {
      annotations <- rbind(annotations, label_coords, loop_label_coords)
    }
  }

  points_df[c("x", "y")] <- lapply(points_df[c("x", "y")], round, 5)

  line_cols <- c("x_start", "y_start", "x_end", "y_end",
                 "ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2")
  lines_df[line_cols] <- lapply(lines_df[line_cols], round, 5)

  annotations[c("x", "y")] <- lapply(annotations[c("x", "y")], round, 5)

  loops_df[c("x_center", "y_center")] <- lapply(loops_df[c("x_center", "y_center")], round, 5)

  list(points = points_df, lines = lines_df, annotations = annotations, loops = loops_df)
}

generate_network_stats_annotations <- function(
    network_obj,
    use_clustering = FALSE,
    clustering_method = NULL,
    net_modularity = FALSE,
    net_clustering = FALSE,
    net_density = FALSE,
    net_avg_path = FALSE,
    text_stats_size = 16,
    text_stats_color = "#000000",
    text_stats_fill = NA,
    text_stats_font = "sans",
    text_stats_fontface = "plain",
    text_stats_alpha = 1,
    text_stats_angle = 0,
    which_group = "1",
    x_stats_location = 0.5,
    y_stats_location = 0.9,
    text_stats_hjust = 0.5,
    text_stats_vjust = 0.5
) {

  if (!(inherits(network_obj, "qgraph") || inherits(network_obj, "igraph"))) {
    return(NULL)
  }

  if (!any(c(net_modularity, net_clustering, net_density, net_avg_path))) {
    return(NULL)
  }

  stats <- calculate_network_stats(
    network_obj = network_obj,
    use_clustering = use_clustering,
    clustering_method = clustering_method
  )

  if (is.null(stats)) {
    return(NULL)
  }

  text_lines <- character(0)

  if (net_modularity && !is.null(stats$modularity)) {
    if (use_clustering && !is.null(clustering_method)) {
      method_name <- switch(clustering_method,
                            "louvain" = "Louvain",
                            "leiden" = "Leiden",
                            "walktrap" = "Walktrap",
                            "fast_greedy" = "Fast Greedy",
                            clustering_method
      )
      text_lines <- c(text_lines, sprintf("Modularity (%s) = %.3f", method_name, stats$modularity))
    } else {
      text_lines <- c(text_lines, "Modularity: Clustering not enabled")
    }
  }

  if (net_clustering && !is.null(stats$clustering)) {
    text_lines <- c(text_lines, sprintf("Clustering Coefficient = %.3f", stats$clustering))
  }

  if (net_density && !is.null(stats$density)) {
    text_lines <- c(text_lines, sprintf("Density = %.3f", stats$density))
  }

  if (net_avg_path && !is.null(stats$avg_path_length)) {
    text_lines <- c(text_lines, sprintf("Average Path Length = %.3f", stats$avg_path_length))
  }

  if (use_clustering && !is.null(stats$n_communities)) {
    text_lines <- c(text_lines, sprintf("Number of Communities = %d", stats$n_communities))
    if (!is.null(stats$community_sizes)) {
      sizes_text <- paste(stats$community_sizes, collapse = ", ")
      text_lines <- c(text_lines, sprintf("Community sizes: %s", sizes_text))
    }
  }

  if (length(text_lines) == 0) {
    return(NULL)
  }

  final_text <- paste(text_lines, collapse = "\n")

  fill_color <- if (!is.null(text_stats_fill) && !is.na(text_stats_fill)) {
    text_stats_fill
  } else {
    NA
  }

  text_df <- data.frame(
    text = final_text,
    x = x_stats_location,
    y = y_stats_location,
    font = text_stats_font,
    size = text_stats_size,
    color = text_stats_color,
    fill = fill_color,
    angle = text_stats_angle,
    alpha = text_stats_alpha,
    fontface = text_stats_fontface,
    math_expression = FALSE,
    hjust = text_stats_hjust,
    vjust = text_stats_vjust,
    lavaan = FALSE,
    network = FALSE,
    locked = FALSE,
    group_label = FALSE,
    loop_label = FALSE,
    group = which_group,
    stringsAsFactors = FALSE
  )

  return(text_df)
}

calculate_network_stats <- function(network_obj, use_clustering = FALSE, clustering_method = NULL) {
  tryCatch({
    if (inherits(network_obj, "qgraph")) {
      edge_colors <- network_obj$graphAttributes$Edges$color

      edgelist <- network_obj$Edgelist

      if (length(edge_colors) > 0) {
        visible_edge_indices <- which(edge_colors != "#00000000")

        if (length(visible_edge_indices) == 0) {
          stats <- list()
          stats$clustering <- 0
          stats$density <- 0
          stats$avg_path_length <- NA
          stats$n_nodes <- length(network_obj$graphAttributes$Nodes$labels)
          stats$n_edges_total <- 0
          stats$n_edges_nonzero <- 0
          return(stats)
        }

        visible_from <- edgelist$from[visible_edge_indices]
        visible_to <- edgelist$to[visible_edge_indices]
        visible_weights <- edgelist$weight[visible_edge_indices]

        n_nodes <- length(network_obj$graphAttributes$Nodes$labels)
        adj_mat <- matrix(0, nrow = n_nodes, ncol = n_nodes)

        for (i in seq_along(visible_edge_indices)) {
          from <- visible_from[i]
          to <- visible_to[i]
          weight <- visible_weights[i]
          adj_mat[from, to] <- weight
          adj_mat[to, from] <- weight
        }

        ig <- igraph::graph_from_adjacency_matrix(adj_mat,
                                                  weighted = TRUE,
                                                  mode = "undirected")

      } else {
        adj_mat <- qgraph::getWmat(network_obj)
        ig <- igraph::graph_from_adjacency_matrix(adj_mat,
                                                  weighted = TRUE,
                                                  mode = "undirected")
      }

    } else if (inherits(network_obj, "igraph")) {
      ig <- network_obj
    } else {
      return(NULL)
    }

    if (igraph::ecount(ig) == 0) return(NULL)

    stats <- list()

    edge_weights <- igraph::E(ig)$weight

    non_zero_indices <- which(abs(edge_weights) > .Machine$double.eps)

    if (length(non_zero_indices) == 0) {
      stats$clustering <- 0
      stats$density <- 0
      stats$avg_path_length <- NA
      stats$n_nodes <- igraph::vcount(ig)
      stats$n_edges_total <- 0
      stats$n_edges_nonzero <- 0
      return(stats)
    }

    ig_nonzero <- igraph::subgraph.edges(ig, non_zero_indices)
    non_zero_weights <- edge_weights[non_zero_indices]

    stats$clustering <- igraph::transitivity(ig_nonzero, type = "global", weights = NA)

    n_nodes <- igraph::vcount(ig)
    max_possible_edges <- n_nodes * (n_nodes - 1) / 2
    stats$density <- length(non_zero_weights) / max_possible_edges

    if (igraph::is_connected(ig_nonzero)) {
      if (any(non_zero_weights < 0, na.rm = TRUE)) {
        stats$avg_path_length <- igraph::mean_distance(ig_nonzero, weights = NA)
      } else {
        stats$avg_path_length <- igraph::mean_distance(ig_nonzero)
      }
    }

    # Negative edges count
    n_negative <- sum(non_zero_weights < 0, na.rm = TRUE)
    if (n_negative > 0) {
      stats$n_negative_edges <- n_negative
      stats$prop_negative_edges <- n_negative / length(non_zero_weights)
    }

    if (use_clustering && !is.null(clustering_method)) {

      ig_cluster <- igraph::as.undirected(ig_nonzero)  # Use only non-zero edges

      if (any(non_zero_weights < 0, na.rm = TRUE)) {
        n_negative <- sum(non_zero_weights < 0, na.rm = TRUE)
        igraph::E(ig_cluster)$weight <- abs(non_zero_weights)
      }

      if (any(non_zero_weights == 0, na.rm = TRUE)) {
        zero_weights <- which(non_zero_weights == 0)
        igraph::E(ig_cluster)$weight[zero_weights] <- 0.0001
      }

      comm <- tryCatch({
        switch(
          clustering_method,
          "louvain" = igraph::cluster_louvain(ig_cluster, weights = igraph::E(ig_cluster)$weight),
          "leiden" = {
            if (requireNamespace("igraph", quietly = TRUE) &&
                "cluster_leiden" %in% getNamespaceExports(asNamespace("igraph"))) {
              igraph::cluster_leiden(ig_cluster,
                                     weights = igraph::E(ig_cluster)$weight,
                                     objective_function = "modularity")
            } else {
              igraph::cluster_louvain(ig_cluster, weights = igraph::E(ig_cluster)$weight)
            }
          },
          "walktrap" = igraph::cluster_walktrap(ig_cluster, weights = igraph::E(ig_cluster)$weight),
          "fast_greedy" = igraph::cluster_fast_greedy(ig_cluster, weights = igraph::E(ig_cluster)$weight)
        )
      }, error = function(e) {
        tryCatch({
          igraph::cluster_spinglass(ig_cluster, weights = if(!is.null(igraph::E(ig_cluster)$weight))
            abs(igraph::E(ig_cluster)$weight))
        }, error = function(e2) {
          tryCatch({
            igraph::cluster_edge_betweenness(ig_cluster, weights = if(!is.null(igraph::E(ig_cluster)$weight))
              abs(igraph::E(ig_cluster)$weight))
          }, error = function(e3) {
            igraph::components(ig_cluster)$membership
          })
        })
      })

      if (!is.null(comm)) {
        community_membership <- NULL
        modularity_value <- NA

        if (inherits(comm, "communities")) {
          community_membership <- igraph::membership(comm)
          modularity_value <- tryCatch({
            igraph::modularity(comm)
          }, error = function(e) {
            tryCatch({
              igraph::modularity(ig_cluster, community_membership)
            }, error = function(e2) {
              NA
            })
          })
        } else if (is.numeric(comm) || is.integer(comm)) {
          community_membership <- comm
          modularity_value <- tryCatch({
            igraph::modularity(ig_cluster, community_membership)
          }, error = function(e) {
            NA
          })
        } else {
          community_membership <- rep(1, igraph::vcount(ig_cluster))
          modularity_value <- NA
        }

        stats$modularity <- modularity_value

        if (!is.null(community_membership)) {
          stats$n_communities <- length(unique(community_membership))
          stats$community_sizes <- as.numeric(table(community_membership))

          if (is.na(stats$n_communities) || stats$n_communities <= 0) {
            stats$n_communities <- 1
          }
        } else {
          stats$n_communities <- 1
          stats$community_sizes <- igraph::vcount(ig_cluster)
        }

      } else {
        stats$modularity <- NA
        stats$n_communities <- 1
        stats$community_sizes <- igraph::vcount(ig_cluster)
      }
    }

    # Basic counts
    stats$n_nodes <- n_nodes
    stats$n_edges_total <- igraph::ecount(ig)
    stats$n_edges_nonzero <- length(non_zero_weights)

    # Edge weight statistics
    if (length(non_zero_weights) > 0) {
      stats$avg_abs_weight <- mean(abs(non_zero_weights), na.rm = TRUE)
      stats$max_abs_weight <- max(abs(non_zero_weights), na.rm = TRUE)
      stats$min_weight <- min(non_zero_weights, na.rm = TRUE)
      stats$max_weight <- max(non_zero_weights, na.rm = TRUE)
    }

    return(stats)

  }, error = function(e) {
    showNotification(
      paste("Error calculating network statistics:", e$message),
      type = "error",
      duration = 5
    )
    message("Error in calculate_network_stats: ", e$message)
    return(NULL)
  })
}
auto_generate_edges <- function(points_data, layout_type = "fully_connected", line_color = "#000000",
                                line_width = 2, line_alpha = 1, line_style = "solid", random_prob = 0.1, particular_node = NULL,
                                auto_endpoint_spacing = 0, random_seed = NULL,
                                bezier_auto_edges = FALSE,
                                auto_edges_curvature_magnitude = 0.5,
                                auto_edges_rotate_curvature = FALSE,
                                auto_edges_curvature_asymmetry = 0,
                                directed = FALSE,
                                arrow_location = "start",
                                arrow_type = 'closed',
                                arrow_size = 0.2,
                                two_way_arrow = FALSE,
                                which_group = "1") {

  if (!is.null(random_seed)) {
    set.seed(random_seed)  # Set the seed if provided
  }

  # Filter out locked nodes
  unlocked_points <- points_data[
    !points_data$locked & !points_data$lavaan & !points_data$network & points_data$group == which_group,
    c("x", "y", "shape", "size", "width_height_ratio", "orientation")
  ]

  if (nrow(unlocked_points) < 2) {
    return(NULL)
  }

  # Extract coordinates for unlocked points
  coord_matrix <- as.matrix(unlocked_points[, c("x", "y")])

  edge_list <- NULL

  # Fully connected layout
  if (layout_type == "fully_connected") {
    g <- make_full_graph(nrow(unlocked_points))
    edge_list <- as_edgelist(g)
  } else if (layout_type == "nearest_neighbor") {
    g <- make_empty_graph(n = nrow(unlocked_points))
    dist_matrix <- as.matrix(dist(coord_matrix))
    for (i in 1:nrow(dist_matrix)) {
      dist_matrix[i, i] <- Inf
      nearest_neighbor <- which.min(dist_matrix[i, ])
      if (!is.na(nearest_neighbor)) {
        g <- add_edges(g, c(i, nearest_neighbor))
      }
    }
    edge_list <- as_edgelist(g)
  } else if (layout_type == "connect_to_central_node") {
    center_point <- colMeans(coord_matrix)
    dist_to_center <- apply(coord_matrix, 1, function(row) sqrt(sum((row - center_point)^2)))
    central_node_index <- which.min(dist_to_center)
    edge_list <- cbind(central_node_index, setdiff(1:nrow(coord_matrix), central_node_index))
  } else if (layout_type == "connect_to_particular_node") {
    if (!is.null(particular_node)) {
      selected_node <- which(rownames(unlocked_points) == particular_node)
      if (length(selected_node) == 0) {
        return(NULL) # If selected node = invalid, exit
      }

      edge_list <- cbind(selected_node, setdiff(1:nrow(unlocked_points), selected_node))
    }
  } else if (layout_type == "random_graph") {
    g <- erdos.renyi.game(nrow(unlocked_points), p.or.m = random_prob, directed = FALSE)
    edge_list <- as_edgelist(g)
  }

  # Check if edge_list is valid and has proper indices
  if (!is.null(edge_list) && nrow(edge_list) > 0 && all(edge_list[, 1] <= nrow(coord_matrix)) && all(edge_list[, 2] <= nrow(coord_matrix))) {
    lines_df <- data.frame(
      x_start = coord_matrix[edge_list[, 1], 1],
      y_start = coord_matrix[edge_list[, 1], 2],
      x_end = coord_matrix[edge_list[, 2], 1],
      y_end = coord_matrix[edge_list[, 2], 2],
      ctrl_x = NA,
      ctrl_y = NA,
      ctrl_x2 = NA,
      ctrl_y2 = NA,
      curvature_magnitude = 0,
      curvature_asymmetry = 0,
      rotate_curvature = FALSE,
      type = ifelse(bezier_auto_edges == FALSE, ifelse(directed, "Straight Arrow", "Straight Line"), ifelse(directed, "Curved Arrow", "Curved Line")),
      color = line_color,
      end_color = NA,
      color_type = "Single",
      gradient_position = NA,
      width = line_width,
      alpha = line_alpha,
      arrow = ifelse(directed, directed, NA),
      arrow_type = ifelse(directed, arrow_type, NA),
      arrow_size = ifelse(directed, arrow_size, NA),
      two_way = ifelse(directed, two_way_arrow, NA),
      lavaan = FALSE,
      network = FALSE,
      line_style = line_style,
      locked = FALSE,
      group = which_group,
      stringsAsFactors = FALSE
    )

    lines_df <- adjust_edge_coordinates(
      lines_df = lines_df,
      edge_list = edge_list,
      points_df = unlocked_points,
      auto_endpoint_spacing = auto_endpoint_spacing
    )

    if (bezier_auto_edges == TRUE) {
      bezier_indices <- which(lines_df$type %in% c('Curved Arrow', 'Curved Line'))

      control_points <- mapply(
        calculate_control_point,
        x_start = lines_df$x_start[bezier_indices],
        y_start = lines_df$y_start[bezier_indices],
        x_end = lines_df$x_end[bezier_indices],
        y_end = lines_df$y_end[bezier_indices],
        curvature_magnitude = auto_edges_curvature_magnitude,
        rotate_curvature = auto_edges_rotate_curvature,
        curvature_asymmetry = auto_edges_curvature_asymmetry,
        SIMPLIFY = FALSE
      )

      # Assign the calculated control points to lines
      lines_df$ctrl_x[bezier_indices] <- sapply(control_points, `[[`, "ctrl_x")
      lines_df$ctrl_y[bezier_indices] <- sapply(control_points, `[[`, "ctrl_y")
      lines_df$ctrl_x2[bezier_indices] <- sapply(control_points, `[[`, "ctrl_x2")
      lines_df$ctrl_y2[bezier_indices] <- sapply(control_points, `[[`, "ctrl_y2")
      lines_df$locked[bezier_indices] <- FALSE
    }

    if (arrow_location == "start") {
      # Swap start and end coordinates
      temp_x <- lines_df$x_start
      temp_y <- lines_df$y_start
      lines_df$x_start <- lines_df$x_end
      lines_df$y_start <- lines_df$y_end
      lines_df$x_end <- temp_x
      lines_df$y_end <- temp_y
    }

    # round five decimal places
    lines_df$x_start <- round(lines_df$x_start, 5)
    lines_df$y_start <- round(lines_df$y_start, 5)
    lines_df$x_end <- round(lines_df$x_end, 5)
    lines_df$y_end <- round(lines_df$y_end, 5)


    return(lines_df)
  } else {
    return(NULL)
  }
}


auto_layout_points <- function(points_data, layout_type = "layout_in_circle", distance = 1,
                               center_x = 0, center_y = 0, orientation = 0,
                               curvature_magnitude = 0.2, flip_curve = FALSE, asymmetry = 0,
                               random_seed = NULL, which_group) {

  if (!is.null(random_seed)) {
    set.seed(random_seed)  # Set the seed if provided
  }

  if (!"locked" %in% names(points_data)) {
    points_data$locked <- FALSE
  }

  unlocked_points <- points_data[!points_data$locked & !points_data$lavaan & points_data$group == which_group, ]
  n <- nrow(unlocked_points)
  # curvature_magnitude <- curvature_magnitude * 100
  if (layout_type == "curved_line") {

    angle_rad <- orientation * pi / 180
    dx <- cos(angle_rad)
    dy <- sin(angle_rad)

    x_start <- center_x - distance * (n - 1) / 2 * dx
    y_start <- center_y - distance * (n - 1) / 2 * dy
    x_end <- center_x + distance * (n - 1) / 2 * dx
    y_end <- center_y + distance * (n - 1) / 2 * dy

    seg_dx <- x_end - x_start
    seg_dy <- y_end - y_start
    seg_length <- sqrt(seg_dx^2 + seg_dy^2)

    perp_x <- -seg_dy/seg_length
    perp_y <- seg_dx/seg_length


    ctrl_x <- x_start + seg_dx/3 + perp_x * curvature_magnitude * seg_length * (1 + asymmetry)
    ctrl_y <- y_start + seg_dy/3 + perp_y * curvature_magnitude * seg_length * (1 + asymmetry)

    ctrl2_x <- x_end - seg_dx/3 + perp_x * curvature_magnitude * seg_length * (1 - asymmetry)
    ctrl2_y <- y_end - seg_dy/3 + perp_y * curvature_magnitude * seg_length * (1 - asymmetry)


    if (flip_curve) {
      mid_x <- (x_start + x_end)/2
      mid_y <- (y_start + y_end)/2
      ctrl_x <- 2*mid_x - ctrl_x
      ctrl_y <- 2*mid_y - ctrl_y
      ctrl2_x <- 2*mid_x - ctrl2_x
      ctrl2_y <- 2*mid_y - ctrl2_y
    }

    t <- seq(0, 1, length.out = n)
    unlocked_points$x <- (1-t)^3*x_start + 3*(1-t)^2*t*ctrl_x + 3*(1-t)*t^2*ctrl2_x + t^3*x_end
    unlocked_points$y <- (1-t)^3*y_start + 3*(1-t)^2*t*ctrl_y + 3*(1-t)*t^2*ctrl2_y + t^3*y_end

  } else if (layout_type == "straight_line") {
    angle_rad <- orientation * pi / 180
    dx <- cos(angle_rad)
    dy <- sin(angle_rad)

    unlocked_points$x <- seq(center_x - distance * (n - 1) / 2 * dx,
                             center_x + distance * (n - 1) / 2 * dx,
                             length.out = n
    )
    unlocked_points$y <- seq(center_y - distance * (n - 1) / 2 * dy,
                             center_y + distance * (n - 1) / 2 * dy,
                             length.out = n
    )
  } else {
    g <- make_empty_graph(n = nrow(unlocked_points))
    layout_fun <- match.fun(layout_type)
    layout_coords <- layout_fun(g) * distance

    angle_rad <- orientation * pi / 180
    rotated_coords <- data.frame(
      x = layout_coords[, 1] * cos(angle_rad) - layout_coords[, 2] * sin(angle_rad),
      y = layout_coords[, 1] * sin(angle_rad) + layout_coords[, 2] * cos(angle_rad)
    )
    unlocked_points$x <- round(rotated_coords$x + center_x, 5)
    unlocked_points$y <- round(rotated_coords$y + center_y, 5)
  }

  points_data[!points_data$locked & !points_data$lavaan & !points_data$network & points_data$group == which_group, ] <- unlocked_points
  return(points_data)
}


ui <- fluidPage(
  shinyjs::useShinyjs(),
  tags$head(
    tags$style(HTML("
      .sidebar {
        height: 140vh;
        overflow-y: auto;
        padding: 15px;
        background-color: #eff1f3;
      }
      .conditional-panel {
      background-color: #eff1f3; /* Light blue-gray background */
      color: #333; /* Dark text */
      border: none;
      border-radius: 10px; /* Rounded corners */
      padding: 20px; /* Internal spacing */
      }
      /* Sidebar headers */
      .sidebar h4, .sidebar h5 {
        color: black; /* Lighter text color for headers */
        font-weight: bold;
      }
      .scrollable-tables {
      height: 400px;
      overflow-y: auto;
      border: 1px solid #ddd;
      padding: 10px;
      background-color: white;
      font-size: 14px;
    }
    .scrollable-tables h4 {
      margin-top: 20px;
    }
    /* Style for toggle buttons */
      .toggle-button {
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 5px 0;
      }
      .toggle-button:hover {
        text-decoration: underline;
      }
      /* Chevron rotation */
      .toggle-button .fas {
        transition: transform 0.2s;
      }
      .toggle-button.collapsed .fas {
        transform: rotate(-90deg);
      }
      .redo-button {
      background-color: #f0f1f3;
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 16px;
      cursor: pointer;
      }
      .redo-button:hover {
      background-color: #d4d4d4; /* Darker on hover */
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 17px;
      cursor: pointer;
      transition: all 0.3s ease;
      }
      .redo-button-main {
      background-color: #f0f1f3;
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 18px;
      cursor: pointer;
      }
      .redo-button-main:hover {
      background-color: #d4d4d4; /* Darker on hover */
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 19px;
      cursor: pointer;
      transition: all 0.3s ease;
      }
      .redo-button-main2 {
      background-color: #f8f9fa;
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 18px;
      cursor: pointer;
      }
      .redo-button-main2:hover {
      background-color: #d4d4d4; /* Darker on hover */
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 19px;
      cursor: pointer;
      transition: all 0.3s ease;
      }
      .redo-button0 {
      background-color: white;
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 14px;
      cursor: pointer;
      }
      .redo-button0:hover {
      background-color: #f0f1f3; /* Darker on hover */
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 14px;
      cursor: pointer;
      transition: all 0.3s ease;
      }
      .redo-button01 {
      background-color: #f8f9fa;
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 14px;
      cursor: pointer;
      }
      .redo-button01:hover {
      background-color: #f0f1f3; /* Darker on hover */
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 14px;
      cursor: pointer;
      transition: all 0.3s ease;
      }
      .redo-button02 {
      background-color: #f0f1f3;
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 14px;
      cursor: pointer;
      }
      .redo-button02:hover {
      background-color: #d4d4d4; /* Darker on hover */
      color: black;
      border: none;
      border-radius: 5px;
      padding: 10px;
      font-size: 14px;
      cursor: pointer;
      transition: all 0.3s ease;
      }
      .custom-select .selectize-control {
      background-color: #eff0f3;
      border: 1px solid #d1d5d8;
      border-radius: 4px;
      padding: 2px;
      box-shadow: none;
      }
      .input-container {
        margin-bottom: 20px;
        padding: 15px;
        background-color: #f8f9fa;
        border-radius: 5px;
        border: 1px solid #dee2e6;
      }
      .input-header {
        display: flex;
        align-items: center;
        margin-bottom: 10px;
        color: #495057;
      }
      .input-header i {
        margin-right: 10px;
      }
      .numeric-input {
        margin-top: 10px;
      }
      .slider-container {
        margin-bottom: 10px;
      }

      /* Input field inside the dropdown */
      .custom-select .selectize-input {
      background-color: white;
      color: #333333;
      font-size: 14px;
      border: none;
      box-shadow: none;
      padding: 8px;
      }

      /* Dropdown menu items */
      .custom-select .selectize-dropdown {
      background-color: white;
      border: 1px solid #d1d5d8;
      border-radius: 4px;
      box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
      }

      /* Hover effect for dropdown items */
      .custom-select .selectize-dropdown-content .option:hover {
      background-color: #eff1f3;
      color: #000000;
      }

      /* Tab headers hover effect */
      .nav-tabs > li > a:hover {
        background-color: #e2e8f0;
        color: #1a202c;
        border-color: #cbd5e0;
        transition: all 0.3s ease;
      }

      /* Active tab header style */
      .nav-tabs > li.active > a,
      .nav-tabs > li.active > a:hover {
        background-color: #eff1f3;
        color: #333333;
        border: 1px solid #d1d5d8;
      }

      /* Adjust tab panel hover (content area inside tabs) */
      .tab-content:hover {
        background-color: #f8fafc;
        transition: background-color 0.3s ease;
      }

      /* Table row hover inside tabs */
      .tab-content table.dataTable tbody tr:hover {
        background-color: #eff1f3;
        color: #1a202c;
        transition: background-color 0.3s ease, color 0.3s ease;
      }

      /* Table header hover inside tabs */
      .tab-content table.dataTable thead th:hover {
        background-color: #d9dee1;
        color: #000; /* Darker text color */
        transition: background-color 0.3s ease, color 0.3s ease;
      }
    "))
  ),
  tags$head(
    tags$title("ggsem: Interactive & Reproducible Visualizations of SEM Diagrams")
  ),
  titlePanel(
    tags$a(
      href = "https://smin95.github.io/ggsem",
      target = "_blank",
      style = "text-decoration: none; color: inherit; display: block; text-align: center; margin: 20px 0;",
      HTML(
        paste(
          "<div style='font-size: 28px; font-weight: bold; text-transform: lowercase; color: #4B5563; '>
          ggsem:
        </div>",
          "<div style='font-size: 18px; color: #1F2937; letter-spacing: 2px;'>
          Interactive ",
          as.character(icon("mouse-pointer", style = "margin-left: 6px; color: #1F2937;")),
          " and Reproducible ",
          as.character(icon("sync-alt", style = "margin-left: 6px; color: #4B5563;")),
          "</div>",
          "<div style='font-size: 20px; background: linear-gradient(to right, #6B7280, #4B5563); -webkit-background-clip: text; color: transparent;'>
          Visualizations of Networks and SEM Diagrams
        </div>",
          as.character(icon("circle-nodes", style = "margin-top: 10px; color: #6B7280; font-size: 24px;")),
          as.character(icon("project-diagram", style = "margin-top: 10px; color: #6B7280; font-size: 24px;"))
        )
      )
    )
  ),
  sidebarLayout(
    sidebarPanel(
      class = "sidebar", # Apply the custom CSS class
      width = 4,
      h4(
        tagList(
          icon("mouse-pointer", style = "margin-right: 8px;"),
          "Element Selection"
        )
      ),
      div(
        class = "custom-select", # Apply custom class
        selectInput(
          "element_type",
          "Choose Element Type:",
          choices = c("Point", "Line", "Text Annotation", "Self-loop Arrow", "SEM Diagram", "Network Diagram", "Aesthetic Grouping")
        )
      ),

      # Layer ordering elements
      selectizeInput(
        "layer_order",
        label = tagList(
          icon("layer-group", style = "margin-right: 8px;"),
          "Drag to Reorder Layers (Front = 1st; Back = 4th):"
        ),
        choices = list(
          "Annotations" = "annotations",
          "Self-loop arrows" = "loops",
          "Points" = "points",
          "Lines" = "lines"
        ),
        selected = c("annotations", "loops", "points", "lines"),
        multiple = TRUE,
        options = list(
          placeholder = 'Drag layers to reorder (top = front)',
          plugins = list('drag_drop', 'remove_button'),
          create = FALSE,
          maxItems = 4,
          dropdownParent = 'body'
        )
      ),
      conditionalPanel(
        condition = "['Point', 'Line', 'Text Annotation', 'Self-loop Arrow', 'SEM Diagram', 'Network Diagram', 'Aesthetic Grouping'].includes(input.element_type)",
        fluidRow(
          column(6, selectInput(
            "group_select",
            label = tagList(
              icon("object-group", style = "margin-right: 8px;"),
              "Which Group to Modify:"
            ),
            choices = NULL)
          ),
          column(6,
                 textInput("which_group",
                           label = tagList(
                             icon("plus-square", style = "margin-right: 8px;"),
                             "Assign Group:"
                           ),
                           value = "1",
                           placeholder = "To newly added element(s)")
          )
        )
      ),
      div(style = "margin-top: 10px;"),
      fluidRow(
        column(
          3,
          div(class = "input-container",
              div(class = "input-header",
                  icon("search-plus"),
                  h5("Zoom Control", style = "margin: 0;")
              ),
              div(class = "numeric-input",
                  numericInput(
                    "zoom_numeric",
                    label = "0.1 to 10:",
                    value = 1.2, min = 0.1, max = 10, step = 0.1
                  )
              )
          )
        ),
        column(
          3,
          div(class = "input-container",
              div(class = "input-header",
                  icon("arrows-alt-h"),
                  h5("Horizontal Shift", style = "margin: 0;")
              ),
              div(class = "numeric-input",
                  numericInput(
                    "horizontal_numeric",
                    label = "-100 to 100:",
                    value = 0, min = -100, max = 100, step = 1
                  )
              )
          )
        ),
        column(
          3,
          div(class = "input-container",
              div(class = "input-header",
                  icon("arrows-alt-v"),
                  h5("Vertical Shift", style = "margin: 0;")
              ),
              div(class = "numeric-input",
                  numericInput(
                    "vertical_numeric",
                    label = "-100 to 100:",
                    value = 0, min = -100, max = 100, step = 1
                  )
              )
          )
        ),
        column(
          3,
          div(class = "button-container",
              style = "padding-top: 25px;",  # Adjust to align with inputs
              actionButton(
                "change_view",
                class = "redo-button02",
                label = tagList(icon("check-double"), "Change View"),
                style = "width: 100%; height: 50px; display: flex; align-items: center; justify-content: center; gap: 10px; font-size: 15px;"
              )
          )
        ),
      ),
      fluidRow(
        column(9,
        ),
        column(3,
               checkboxInput("show_grid",
                             tags$span(icon("th"), " Show Grid"),
                             FALSE)
        )
      ),
      fluidRow(
        column(
          12, # Make the column span the full width
          div(
            class = "text-center", # Bootstrap class for centering content
            actionButton("undo_button", class = "redo-button", label = tagList(icon("undo"), "Undo")),
            actionButton("redo_button", class = "redo-button", label = tagList(icon("redo"), "Redo")),
            actionButton("delete_everything", class = "redo-button", label = tagList(icon("trash"), "Clear")) # New Clear button
          )
        )
      ),
      #div(style = "margin-top: 10px;"),
      conditionalPanel(
        condition = "input.element_type == 'Point'",
        #shiny::wellPanel(
        class = "conditional-panel",
        tags$div(
          class = "panel-group",
          style = "margin: 0; padding: 0;",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subPointInputs",
            `aria-expanded` = "false",
            `aria-controls` = "subPointInputs",
            tags$h4(
              tagList(
                tags$span(icon("plus-circle", style = "margin-right: 8px;"),
                          title = "Add points before drawing networks or node connections."),
                h5(HTML("<b style='font-size: 16px;'>Draw Individual Points</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subPointInputs",
            class = "panel-collapse collapse",
            fluidRow(
              column(6, numericInput("x_coord", "X Coordinate:", "0")),
              column(6, numericInput("y_coord", "Y Coordinate:", "0"))
            ),
            shiny::wellPanel(style = "background: #f8f9fa;",
                             fluidRow(
                               column(6, colourpicker::colourInput("point_color", "Point Color:", value = "#000000")),
                               column(6, div(class = "custom-select", selectInput("shape",
                                                                                  HTML(paste(
                                                                                    "Select Shape"
                                                                                  )),
                                                                                  choices = c("circle", "square", "rectangle", "oval", "triangle", "diamond")
                               )))
                             ),
                             fluidRow(
                               column(6, numericInput("point_size", "Point Size:", value = 15, min = 1)),
                               column(6, numericInput("border_width", "Border Width:", value = 1, min = 0))
                             ),
                             fluidRow(
                               column(6, colourpicker::colourInput("border_color", "Border Color:", value = "white")),
                               column(6, numericInput("point_alpha", "Point Alpha:", value = 1, min = 0, max = 1, step = 0.1)) # Alpha input for points
                             ),
                             fluidRow(
                               column(
                                 6,
                                 numericInput(
                                   "point_orientation",
                                   label = HTML(paste(
                                     icon("sync-alt", style = "margin-right: 8px;"), "Orientation (Degrees):"
                                   )),
                                   value = 0,
                                   min = 0,
                                   max = 360,
                                   step = 1
                                 )
                               ),
                               conditionalPanel(
                                 condition = "input.shape == 'rectangle' || input.shape == 'oval' || input.shape == 'diamond'",
                                 column(
                                   6,
                                   div(
                                     numericInput(
                                       "width_height_ratio",
                                       label = HTML(paste(
                                         icon("ruler-combined", style = "margin-right: 8px;"), "Width/Height Ratio"
                                       )),
                                       value = 1.6,
                                       min = 0.1,
                                       step = 0.1
                                     ),
                                     tags$span(
                                       icon("question-circle"),
                                       title = "Adjust the ratio of width to height for rectangle, oval, and diamond shapes.",
                                       style = "cursor: help; margin-left: 6px; color: #007bff;"
                                     ),
                                     style = "display: flex; align-items: center;"
                                   )
                                 )
                               )
                             ),
                             tags$div(
                               style = "position: relative;",
                               #style = "position: absolute; bottom: 10px; right: 10px; font-size: 12px; color: #007bff;",
                               tags$span(
                                 icon("info-circle", style = "margin-right: 6px;"),
                                 "These inputs support aesthetic grouping for unlocked points."
                               )
                             )
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "add_point",
                class = "redo-button-main",
                label = tagList(icon("plus-circle"), HTML("&nbsp;Add Point"))
              ),
              style = "text-align: center;" # Center the button
            )
          )
        ),
        div(style = "margin-top: 10px;"),
        tags$div(
          class = "panel-group",
          style = "margin: 0; padding: 0;",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subDrawNetworks",
            `aria-expanded` = "false",
            `aria-controls` = "subDrawNetworks",
            tags$h4(
              tagList(
                tags$span(icon("project-diagram", style = "margin-right: 8px;"),
                          title = "Points need to be drawn before using the network layout feature."),
                h5(HTML("<b style='font-size: 16px;'>Sort Points in Layout</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subDrawNetworks",
            class = "panel-collapse collapse",
            fluidRow(
              column(12, selectInput("layout_type",
                                     HTML(paste(
                                       icon("project-diagram", style = "margin-right: 6px;"),
                                       "Layout Type"
                                     )),
                                     choices = c(
                                       "Circle" = "layout_in_circle",
                                       "Grid" = "layout_on_grid",
                                       "Random" = "layout_randomly",
                                       "Star" = "layout_as_star",
                                       "Fruchterman-Reingold" = "layout_with_fr",
                                       "Kamada-Kawai" = "layout_with_kk",
                                       "Straight Line" = "straight_line",
                                       "Curved Line" = "curved_line"
                                     )
              ))
            ),
            conditionalPanel(
              condition = "input.layout_type == 'curved_line'",
              fluidRow(
                column(6, numericInput("curvature_magnitude", "Curvature Magnitude:", value = 0.2, min = 0, max = 1, step = 0.05)),
                column(6, checkboxInput("rotate_curvature", "Flip Curve 180°", value = FALSE))
              ),
              fluidRow(
                column(6, numericInput("curvature_asymmetry", "Curvature Asymmetry:", value = 0, min = -1, max = 1, step = 0.1))
              )
            ),

            fluidRow(
              column(
                6,
                numericInput(
                  "point_distance",
                  HTML(paste(
                    icon("ruler-horizontal", style = "margin-right: 6px;"),
                    "Point Distance:"
                  )),
                  value = 10,
                  min = 0.1,
                  step = 0.1
                )
              ),
              column(
                6,
                numericInput(
                  "layout_orientation",
                  HTML(paste(
                    icon("sync-alt", style = "margin-right: 6px;"),
                    "Orientation (Degrees):"
                  )),
                  min = 0,
                  max = 360,
                  value = 0,
                  step = 1
                )
              )
            ),
            fluidRow(
              column(6, numericInput("center_x", "Center X Position:", value = 0)),
              column(6, numericInput("center_y", "Center Y Position:", value = 0))
            ),
            fluidRow(
              column(6, colourpicker::colourInput("grad_start_color", "Gradient Start Color:", value = "blue")),
              column(6, colourpicker::colourInput("grad_end_color", "Gradient End Color:", value = "red")),
              column(6, sliderInput("gradient_position_points", "Gradient Intersection:", min = 0.01, max = 0.99, value = 0.5, step = 0.01),
                     tags$span(
                       icon("question-circle"),
                       title = "The close to 0, the more gradient favors the end color.",
                       style = "cursor: help; margin-left: 6px; color: #007bff;"
                     ),
                     style = "display: flex; align-items: center;"
              )
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "auto_layout",
                class = "redo-button-main",
                label = tags$span(icon("vector-square"), HTML("&nbsp;Auto-layout Points"), title = "Automatically position unlocked points into a selected layout type.")
              ),
              style = "display: flex; align-items: center; justify-content: center;" # Center horizontally
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "apply_gradient",
                class = "redo-button-main",
                label = tags$span(icon("palette"), HTML("&nbsp;Apply Gradient"), title = "Apply a gradient color effect on unlocked points based on the selected start and end colors.")
              ),
              style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Center horizontally with gap
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "lock_points",
                class = "redo-button-main",
                label = tags$span(icon("lock"), HTML("&nbsp;Lock Points"), title = "Prevent points from being moved or modified in the layout or forming automatic edges")
              ),
              style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Ensures alignment and spacing
            )
          )
        ),
        tags$div(
          class = "panel-group",
          style = "margin: 0; padding: 0;",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subAestheticGrouping",
            `aria-expanded` = "false",
            `aria-controls` = "subAestheticGrouping",
            tags$h4(
              tagList(
                tags$span(icon("object-group", style = "margin-right: 8px;"), title = "Apply changes in the positions (and aesthetics) of multiple unlocked points at once."),
                h5(HTML("<b style='font-size: 16px;'>Aesthetic Grouping</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subAestheticGrouping",
            class = "panel-collapse collapse",
            fluidRow(
              column(6, checkboxInput(
                "bulk_shift_point_only",
                HTML(paste(
                  icon("map-marker-alt", title = "If checked, XY positions of unlocked points will also be shifted in group.",
                       style = "margin-right: 6px;"),
                  "Shift XY Only"
                )),
                value = TRUE
              )),
              column(6, checkboxInput(
                "bulk_aesthetics_point_only",
                HTML(paste(
                  icon("paint-brush", title = "If checked, aesthetics of unlocked points will be adjusted in group.",
                       style = "margin-right: 6px;"),
                  "Aesthetics Only"
                )),
                value = FALSE
              )),
              column(
                6,
                numericInput(
                  "bulk_shift_x",
                  label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
                  value = 0,
                  step = 0.1
                )
              ),
              column(
                6,
                numericInput(
                  "bulk_shift_y",
                  label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
                  value = 0,
                  step = 0.1
                )
              )
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "apply_point_bulk_shift",
                class = "redo-button-main",
                label = tagList(icon("check-circle"), HTML("&nbsp;Apply Changes"))
              ),
              style = "display: flex; justify-content: center;"
            )
          )
        )
        #)
      ),
      # Line Inputs in conditionalPanel
      conditionalPanel(
        condition = "input.element_type == 'Line'",
        #shiny::wellPanel(
        class = "conditional-panel",
        tags$div(
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subLineInputs",
            `aria-expanded` = "false",
            `aria-controls` = "subLineInputs",
            tags$h4(
              tagList(
                tags$span(icon("grip-lines", style = "margin-right: 8px;"), title = "Manually draw each line element."),
                h5(HTML("<b style='font-size: 16px;'>Draw Individual Lines</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subLineInputs",
            class = "panel-collapse collapse",
            fluidRow(
              column(6, numericInput("x_start", "Start X Coordinate:", "0")),
              column(6, numericInput("y_start", "Start Y Coordinate:", "0"))
            ),
            fluidRow(
              column(6, numericInput("x_end", "End X Coordinate:", "5")),
              column(6, numericInput("y_end", "End Y Coordinate:", "5"))
            ),
            shiny::wellPanel(style = "background: #f8f9fa;",
                             fluidRow(
                               column(6, colourpicker::colourInput("line_color", "Start Color:", value = "#000000")),
                               column(6, selectInput("color_type", "Line Color Type:", choices = c("Single", "Gradient")))
                             ),
                             conditionalPanel(
                               condition = "input.color_type == 'Gradient'",
                               fluidRow(
                                 column(6, colourpicker::colourInput("end_color", "End Color:", value = "#D64542")),
                                 column(6, sliderInput("gradient_position", "Gradient Intersection:", min = 0.01, max = 0.99, value = 0.5, step = 0.01),
                                        tags$span(
                                          icon("question-circle"),
                                          title = "The close to 0, the more gradient favors the end color.",
                                          style = "cursor: help; margin-left: 6px; color: #007bff;"
                                        ),
                                        style = "display: flex; align-items: center;"
                                 )
                               )
                             ),
                             fluidRow(
                               column(6, numericInput("line_width", "Line Width:", value = 1, min = 1)),
                               column(6, numericInput("line_alpha", "Line Alpha:", value = 1, min = 0, max = 1, step = 0.1))
                             ),
                             fluidRow(
                               column(6, selectInput("line_type", "Line Type:", choices = c("Straight Line", "Straight Arrow", "Curved Line", "Curved Arrow"))),
                               conditionalPanel(
                                 condition = "input.color_type == 'Single'",
                                 column(6, selectInput("line_style", "Line Style:", choices = c("solid", "dashed", "dotted")))
                               )
                             ),

                             # Conditional display for curved lines
                             conditionalPanel(
                               condition = "input.line_type == 'Curved Line' || input.line_type == 'Curved Arrow'",
                               fluidRow(
                                 column(6, sliderInput("curvature_magnitude", "Curvature Magnitude:", min = 0, max = 2, value = 0.3, step = 0.01)),
                                 column(6, checkboxInput("rotate_curvature", "Rotate Curvature 180°", value = FALSE))
                               ),
                               fluidRow(
                                 column(6, sliderInput("curvature_asymmetry", "Curvature Asymmetry", min = -1, max = 1, value = 0, step = 0.1))
                               )
                             ),

                             # Conditional display for arrows
                             conditionalPanel(
                               condition = "input.line_type == 'Straight Arrow' || input.line_type == 'Curved Arrow'",
                               fluidRow(
                                 column(6, selectInput("arrow_type", "Arrow Type:", choices = c("open", "closed"))),
                                 column(6, numericInput("arrow_size", "Arrow Size:", value = 0.2, min = 0.1, step = 0.1))
                               ),
                               fluidRow(
                                 column(6, checkboxInput("two_way_arrow", "Two-way Arrow", value = FALSE)) # two-way arrows checkbox
                               )
                             ),
                             tags$div(
                               style = "position: relative;",
                               tags$span(
                                 icon("info-circle", style = "margin-right: 6px;"),
                                 "These inputs support aesthetic grouping for unlocked lines."
                               )
                             )
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "add_line",
                label = tagList(icon("arrows-alt-h"), HTML("&nbsp;&nbsp;Add Line")),
                class = "redo-button-main"
              ),
              style = "text-align: center;"
            )
          )
        ),
        #br(),
        tags$div(
          tags$div(
            class = "panel-group",
            style = "margin: 0; padding: 0;",
            # Header with toggle button
            tags$div(
              class = "toggle-button collapsed",
              `data-toggle` = "collapse",
              `data-target` = "#subAutoEdges",
              `aria-expanded` = "false",
              `aria-controls` = "subAutoEdges",
              tags$h4(
                tagList(
                  tags$span(icon("project-diagram", style = "margin-right: 8px;"),
                            title =  "Define how nodes are connected. Ensure points (unlocked) are added before connecting them."),
                  h5(HTML("<b style='font-size: 16px;'>Auto-generate Edges to Connect Nodes</b>")),
                  tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
                )
              )
            )),
          tags$div(
            id = "subAutoEdges",
            class = "panel-collapse collapse",
            selectInput("edge_type", "Edge Type:", choices = c("Line", "Arrow"), selected = "Line"),
            selectInput("connection_type", "Choose Edge Connection Type:",
                        choices = c(
                          "Fully Connected" = "fully_connected",
                          "Nearest Neighbor" = "nearest_neighbor",
                          "Connect to Central Node" = "connect_to_central_node",
                          "Connect to Particular Node" = "connect_to_particular_node",
                          "Random Graph" = "random_graph"
                        ),
                        selected = "connect_to_central_node"
            ),
            conditionalPanel(
              condition = "input.connection_type == 'connect_to_particular_node'",
              selectInput("particular_node", "Select Central Node:", choices = NULL)
            ),
            fluidRow(
              column(6,
                     colourpicker::colourInput("auto_line_color", "Edge Color:", value = "#000000")
              ),
              column(6,
                     uiOutput("edge_spacing_ui"),
                     tags$span(
                       icon("question-circle"),
                       title = "Adjusts the spacing between the endpoints of lines and their connected nodes.",
                       style = "cursor: help; color: #007bff; margin-top: 8px; display: block;"
                     )
              )
            ),
            fluidRow(
              column(6, numericInput("auto_line_width", "Edge Width:", value = 1, min = 0.1, step = 0.1)),
              column(6, numericInput("auto_line_alpha", "Edge Alpha:", value = 1, min = 0, max = 1, step = 0.1))
            ),
            fluidRow(
              column(6, selectInput("auto_line_style", "Edge Line Style:",
                                    choices = c("solid", "dashed", "dotted"),
                                    selected = "solid"
              )),
              column(6, div(
                style = "margin-top: 24px;",
                actionButton("lock_lines_button", label = HTML(paste(icon("lock"), "Lock Lines")))
              ))
            ),
            conditionalPanel(
              condition = "input.edge_type == 'Arrow'",
              fluidRow(
                column(6, numericInput("arrow_size", "Arrow Size:", value = 0.2, min = 0.1, step = 0.1)),
                column(6, selectInput("arrow_type", "Arrow Type:", choices = c("open", "closed")))
              ),
              fluidRow(
                column(6, checkboxInput("two_way_arrow", "Two-way Arrow", value = FALSE)),
                column(6, selectInput("arrow_location", "Arrowhead Location:",
                                      choices = c("start", "end"),
                                      selected = "end"
                ))
              )
            ),
            h5(HTML("<b style='font-size: 16px;'>Edge Curvature Settings</b>")),
            checkboxInput("bezier_auto_edges", "Curve Auto Edges", value = FALSE),
            conditionalPanel(
              condition = "input.bezier_auto_edges == true",
              fluidRow(
                column(
                  6,
                  tags$span(
                    icon("question-circle"),
                    title = "Control the curvature magnitude of edges.).",
                    style = "cursor: help; margin-left: 6px; color: #007bff;"
                  ),
                  sliderInput(
                    "auto_edges_curvature_magnitude",
                    "Curvature Size:",
                    min = 0,
                    max = 2,
                    value = 0.3,
                    step = 0.01
                  ),
                ),
                column(
                  6,
                  tags$span(
                    icon("question-circle"),
                    title = "Rotate the orientation of the edges by 180 degrees.",
                    style = "cursor: help; margin-left: 6px; color: #007bff;"
                  ),
                  checkboxInput(
                    "auto_edges_rotate_curvature",
                    "Rotate Curvature 180°",
                    value = FALSE
                  )
                )),
              fluidRow(
                column(6, sliderInput("auto_edges_curvature_asymmetry", "Curvature Asymmetry", min = -1, max = 1, value = 0, step = 0.1))
              )
            ),
            h5(HTML("<b style='font-size: 16px;'>Edge Gradient Settings</b>")),
            helpText('Click "Apply Gradient" to apply gradient color schemes on auto-generated lines.'),
            fluidRow(
              column(6, colourpicker::colourInput("line_color_auto", "Gradient Start Color:", value = "blue")),
              column(6, colourpicker::colourInput("end_color_auto", "Gradient End Color:", value = "red")),
              column(6, sliderInput("gradient_position_auto", "Gradient Intersection:", min = 0.01, max = 0.99, value = 0.5, step = 0.01),
                     tags$span(
                       icon("question-circle"),
                       title = "The close to 0, the more gradient favors the end color.",
                       style = "cursor: help; margin-left: 6px; color: #007bff;"
                     ),
                     style = "display: flex; align-items: center;"
              )
            )
          )
        ),
        div(
          actionButton(
            "auto_generate_edges_button",
            class = "redo-button-main",
            label = tags$span(icon("project-diagram"), "Auto-generate Edges", title = "Automatically generate edges between unlocked points with a specific layout (but not locked points).")
          ),
          style = "display: flex; align-items: center; justify-content: center;" # Ensures spacing and centering
        ),
        div(
          actionButton(
            "apply_line_gradient",
            class = "redo-button-main",
            label = tags$span(icon("palette"), HTML("&nbsp;Apply Gradient"), title = "Apply a gradient color effect on unlocked lines based on the selected start and end colors.")
          ),
          style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Center horizontally with gap
        ),
        tags$div(
          class = "panel-group",
          style = "margin: 0; padding: 0;",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subLineGrouping",
            `aria-expanded` = "false",
            `aria-controls` = "subLineGrouping",
            tags$h4(
              tagList(
                tags$span(icon("object-group", style = "margin-right: 8px;"), title = "Apply changes in the positions (and aesthetics) of multiple unlocked lines at once."),
                h5(HTML("<b style='font-size: 16px;'>Aesthetic Grouping</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subLineGrouping",
            class = "panel-collapse collapse",
            fluidRow(
              column(6, checkboxInput(
                "bulk_shift_line_only",
                HTML(paste(
                  icon("map-marker-alt", title = "If checked, XY positions of unlocked lines will be shifted in group.",
                       style = "margin-right: 6px;"),
                  "Shift XY Only"
                )),
                value = TRUE
              )),
              column(6, checkboxInput(
                "bulk_aesthetics_line_only",
                HTML(paste(
                  icon("palette", title = "If checked, aesthetics of unlocked lines such as width, color, and alpha will be adjusted.",
                       style = "margin-right: 6px;"),
                  "Aesthetics Only"
                )),
                value = FALSE)
              ),
              column(
                6,
                numericInput(
                  "line_bulk_shift_x",
                  label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
                  value = 0,
                  step = 0.1
                )
              ),
              column(
                6,
                numericInput(
                  "line_bulk_shift_y",
                  label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
                  value = 0,
                  step = 0.1
                )
              )
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "apply_line_bulk_shift",
                label = tagList(icon("check-circle"), HTML("&nbsp;Apply Changes")),
                class = "redo-button-main"
              ),
              style = "display: flex; justify-content: center;"
            )
          )
        )
        # )
      ),

      # Text Annotation Inputs
      conditionalPanel(
        condition = "input.element_type == 'Text Annotation'",
        #shiny::wellPanel(
        class = "conditional-panel",
        tags$div(
          class = "panel-group",
          style = "margin: 0; padding: 0;",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subTextAdd",
            `aria-expanded` = "false",
            `aria-controls` = "subTextAdd",
            tags$h4(
              tagList(
                icon("pencil-alt", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Draw Individual Annotations</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subTextAdd",
            class = "panel-collapse collapse",
            fluidRow(
              column(12, textInput("annotation_text", "Text:", "Sample Text")),
            ),
            fluidRow(
              column(
                12,
                checkboxInput(
                  "math_expression",
                  HTML(paste(
                    icon("square-root-alt", style = "margin-right: 6px;"),
                    "Use Math Expression"
                  )),
                  value = FALSE
                ),
                tags$span(
                  icon("question-circle"),
                  title = "The syntax should follow the parse() function.",
                  style = "cursor: help; margin-right: 6px; color: #007bff;"
                ),
                style = "display: flex; align-items: right;"
              )
            ),
            fluidRow(
              column(6, numericInput("annotation_x", "X Coordinate:", "0")),
              column(6, numericInput("annotation_y", "Y Coordinate:", "0"))
            ),
            shiny::wellPanel(style = "background: #f8f9fa;",
                             fluidRow(
                               column(6, selectInput("text_font", "Font:",
                                                     choices = c("sans", "serif", "mono"),
                                                     selected = "sans"
                               )),
                               column(6, numericInput("text_size", "Text Size:", value = 20, min = 1))
                             ),
                             fluidRow(
                               column(6, colourpicker::colourInput("text_color", "Color:", value = "#000000")),
                               column(6, numericInput("text_angle", "Angle (deg):", value = 0))
                             ),
                             fluidRow(
                               column(6, numericInput("text_alpha", "Text Alpha:", value = 1, min = 0, max = 1, step = 0.1)),
                               column(6, selectInput("text_fontface", "Fontface:", choices = c("Plain" = "plain", "Bold" = "bold", "Italic" = "italic")))
                             ),
                             fluidRow(
                               column(6, checkboxInput("apply_fill_text_color", "Fill Color for Texts", value = FALSE)),
                               conditionalPanel(
                                 condition = "input.apply_fill_text_color",
                                 column(6, colourpicker::colourInput("text_fill", "Filling Color:", value = "#FFFFFF"))
                               )
                             ),
                             tags$div(
                               style = "position: relative;",
                               #style = "position: absolute; bottom: 10px; right: 10px; font-size: 12px; color: #007bff;",
                               tags$span(
                                 icon("info-circle", style = "margin-right: 6px;"),
                                 "These inputs support aesthetic grouping for unlocked annotations."
                               )
                             )
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "add_annotation",
                label = tagList(icon("font"), HTML("&nbsp;&nbsp;Add Annotation")),
                class = "redo-button-main"
              ),
              style = "display: flex; justify-content: center;"
            )
          )
        ),
        tags$div(
          class = "panel-group",
          style = "margin: 0; padding: 0;",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subAutoText",
            `aria-expanded` = "false",
            `aria-controls` = "subAutoText",
            tags$h4(
              tagList(
                tags$span(icon("project-diagram", style = "margin-right: 8px;"),
                          title = "Automatically generate texts on unlocked points."),
                h5(HTML("<b style='font-size: 16px;'>Auto-generate Texts on Points</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subAutoText",
            class = "panel-collapse collapse",
            fluidRow(
              column(6, selectInput("text_type_auto", "Text Type:",
                                    choices = c("Text Input" = "default",
                                                "Numbers" = "sequence_numbers",
                                                "Alphabets" = "sequence_letters"),
                                    selected = "sequence_numbers")),
              column(6, conditionalPanel(
                condition = "input.text_type_auto != 'default'",
                numericInput("sequence_start_auto", "Start Value:", value = 1, min = 1, step = 1)
              ))
            ),
            conditionalPanel(
              condition = "input.text_type_auto == 'default'",
              fluidRow(
                column(12, textInput("text_auto", "Text:", value = "Text"))
              )),
            fluidRow(
              column(6, colourpicker::colourInput("grad_start_color_texts", "Gradient Start Color:", value = "blue")),
              column(6, colourpicker::colourInput("grad_end_color_texts", "Gradient End Color:", value = "red")),
              column(6, sliderInput("gradient_position_texts", "Gradient Intersection:", min = 0.01, max = 0.99, value = 0.5, step = 0.01),
                     tags$span(
                       icon("question-circle"),
                       title = "The close to 0, the more gradient favors the end color.",
                       style = "cursor: help; margin-left: 6px; color: #007bff;"
                     ),
                     style = "display: flex; align-items: center;"
              )
            )
          )
        ),
        div(
          actionButton(
            "auto_generate_text_button",
            class = "redo-button-main",
            label = tags$span(icon("project-diagram"), "Auto-generate Annotations", title = "Automatically generate texts on unlocked points (but not locked points).")
          ),
          style = "display: flex; align-items: center; justify-content: center;" # Ensures spacing and centering
        ),
        div(
          actionButton(
            "apply_text_gradient",
            class = "redo-button-main",
            label = tags$span(icon("palette"), HTML("&nbsp;Apply Gradient"), title = "Apply a gradient color effect on unlocked texts based on the selected start and end colors.")
          ),
          style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Center horizontally with gap
        ),
        #h4("Text Annotation Inputs"),
        tags$div(
          class = "panel-group",
          style = "margin: 0; padding: 0;",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subTextGroup",
            `aria-expanded` = "false",
            `aria-controls` = "subTextGroup",
            tags$h4(
              tagList(
                tags$span(icon("object-group", style = "margin-right: 8px;"),
                          title = "Apply changes in the positions (and aesthetics) of multiple unlocked annotations at once."),
                h5(HTML("<b style='font-size: 16px;'>Aesthetic Grouping</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subTextGroup",
            class = "panel-collapse collapse",
            fluidRow(
              column(6, div(
                style = "display: flex; align-items: center;",
                checkboxInput(
                  "bulk_shift_annotation_only",
                  HTML(paste(
                    icon("map-marker-alt", title = "If checked, XY positions of unlocked annotations will be shifted in group.",
                         style = "margin-right: 6px;"),
                    "Shift XY Only"
                  )),
                  value = TRUE
                ))),
              column(6, div(
                style = "display: flex; align-items: center;",
                checkboxInput(
                  "bulk_aesthetics_annotation_only",
                  HTML(paste(
                    icon("palette", title = "If checked, aesthetics of unlocked annotations will be adjusted in group.",
                         style = "margin-right: 6px;"),
                    "Aesthetics Only"
                  )),
                  value = FALSE
                )
              )
              ),
              column(
                6,
                numericInput(
                  "annotation_bulk_shift_x",
                  label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
                  value = 0,
                  step = 0.1
                )
              ),
              column(
                6,
                numericInput(
                  "annotation_bulk_shift_y",
                  label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
                  value = 0,
                  step = 0.1
                )
              )
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "apply_annotation_changes",
                class = "redo-button-main",
                label = tagList(icon("check-circle"), HTML("&nbsp;Apply Changes"))
              ),
              style = "display: flex; justify-content: center;" # Center-align the button
            )
          ),
          column(
            12,
            div(
              div(
                actionButton(
                  "lock_annotations_button",
                  class = "redo-button-main",
                  label = tags$span(icon("lock"), HTML("&nbsp;Lock Annotations"), title = "Prevent annotations from being moved or modified in group")
                ),
                style = "display: flex; align-items: center; justify-content: center; gap: 6px;" # Center-align button and icon
              )
            )
          )
        )
        #)
      ),

      # Self-loop Arrow Inputs
      conditionalPanel(
        condition = "input.element_type == 'Self-loop Arrow'",
        #shiny::wellPanel(
        class = "conditional-panel",
        tags$div(
          class = "panel-group",
          style = "margin: 0; padding: 0;",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subSelfLoops",
            `aria-expanded` = "false",
            `aria-controls` = "subSelfLoops",
            tags$h4(
              tagList(
                icon("fas fa-redo-alt", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Draw Self-loop Arrows</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subSelfLoops",
            class = "panel-collapse collapse",
            fluidRow(
              column(6, numericInput("x_center", "X Coordinate (Center):", "0")),
              column(6, numericInput("y_center", "Y Coordinate (Center):", "0"))
            ),
            shiny::wellPanel(style = "background: #f8f9fa;", fluidRow(
              column(6, numericInput("radius", "Radius:", value = 5, min = 0.1)),
              column(6, numericInput("line_width_loop", "Line Width:", value = 1, min = 0.1))
            ),
            fluidRow(
              column(6, colourpicker::colourInput("line_color_loop", "Line Color:", value = "#000000")),
              column(6, numericInput("line_alpha_loop", "Line Alpha:", value = 1, min = 0, max = 1, step = 0.1))
            ),
            fluidRow(
              column(6, selectInput("arrow_type_loop", "Arrow Type:", choices = c("open", "closed"))),
              column(6, numericInput("arrow_size_loop", "Arrow Size:", value = 0.2, min = 0.1, step = 0.1))
            ),
            fluidRow(
              column(6, numericInput("width_loop", "Loop Width:", value = 1, min = 0.1)),
              column(6, numericInput("height_loop", "Loop Height:", value = 1, min = 0.1))
            ),
            fluidRow(
              column(6, numericInput("gap_size_loop", "Gap Size:", value = 0.2, min = 0, max = 1, step = 0.05)),
              column(6, numericInput("orientation_loop", "Orientation (deg):", value = 0, min = -180, max = 180))
            ),
            fluidRow(
              column(12, checkboxInput("two_way_arrow_loop", "Two-way Arrow", value = FALSE)) # checkbox for two-way self-loop arrows
            ),
            tags$div(
              style = "position: relative;",
              #style = "position: absolute; bottom: 10px; right: 10px; font-size: 12px; color: #007bff;",
              tags$span(
                icon("info-circle", style = "margin-right: 6px;"),
                "These inputs support aesthetic grouping for unlocked self-loop arrows."
              )
            )
            ))
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "add_loop",
                label = tagList(
                  tags$i(
                    class = "fas fa-redo-alt",
                    style = "transform: rotate(135deg); margin-right: 5px;"
                  ),
                  "Add Self-loop Arrow"
                ),
                class = 'redo-button-main'
              ),
              style = "display: flex; justify-content: center;"
            )
          )
        ),
        tags$div(
          tags$div(
            class = "panel-group",
            style = "margin: 0; padding: 0;",
            # Header with toggle button
            tags$div(
              class = "toggle-button collapsed",
              `data-toggle` = "collapse",
              `data-target` = "#subAutoLoops",
              `aria-expanded` = "false",
              `aria-controls` = "subAutoLoops",
              tags$h4(
                tagList(
                  tags$span(icon("project-diagram", style = "margin-right: 8px;"),
                            title =  "Custom how self-loop arrows are automatically generated."),
                  h5(HTML("<b style='font-size: 16px;'>Customize Auto-generated Arrows</b>")),
                  tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
                )
              )
            )),
          tags$div(
            id = "subAutoLoops",
            class = "panel-collapse collapse",
            fluidRow(
              column(
                6,
                numericInput(
                  "loop_offset_x",
                  label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "X Offset From Point")),
                  value = 0,
                  step = 0.1
                )
              ),
              column(
                6,
                numericInput(
                  "loop_offset_y",
                  label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Y Offset From Point")),
                  value = -2,
                  step = 0.1
                )
              ),
              column(6, colourpicker::colourInput("grad_start_color_loops", "Gradient Start Color:", value = "blue")),
              column(6, colourpicker::colourInput("grad_end_color_loops", "Gradient End Color:", value = "red")),
              column(6, sliderInput("gradient_position_loops", "Gradient Intersection:", min = 0.01, max = 0.99, value = 0.5, step = 0.01),
                     tags$span(
                       icon("question-circle"),
                       title = "The close to 0, the more gradient favors the end color.",
                       style = "cursor: help; margin-left: 6px; color: #007bff;"
                     ),
                     style = "display: flex; align-items: center;"
              )
            )
          )
        ),
        div(
          actionButton(
            "auto_generate_loops_button",
            class = "redo-button-main",
            label = tags$span(icon("project-diagram"), "Auto-generate Self-loop Arrows", title = "Automatically generate loop arrows on unlocked points (but not locked points).")
          ),
          style = "display: flex; align-items: center; justify-content: center;" # Ensures spacing and centering
        ),
        div(
          actionButton(
            "apply_loop_gradient",
            class = "redo-button-main",
            label = tags$span(icon("palette"), HTML("&nbsp;Apply Gradient"), title = "Apply a gradient color effect on unlocked loop arrows based on the selected start and end colors.")
          ),
          style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Center horizontally with gap
        ),
        #h4("Self-loop Arrow Inputs"),
        tags$div(
          class = "panel-group",
          style = "margin: 0; padding: 0;",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subLoopChange",
            `aria-expanded` = "false",
            `aria-controls` = "subLoopChange",
            tags$h4(
              tagList(
                tags$span(icon("object-group", style = "margin-right: 8px;"),
                          title = "Modify existing self-loop's arrow. Only works in unlocked state. Modifies gap size and orientation only."),
                h5(HTML("<b style='font-size: 16px;'>Aesthetic Grouping</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subLoopChange",
            class = "panel-collapse collapse",
            fluidRow(
              column(6,
                     checkboxInput(
                       "bulk_shift_loop_only",
                       HTML(paste(
                         icon("map-marker-alt", title = "If checked, XY positions of unlocked loop arrows will be shifted in group.",
                              style = "margin-right: 6px;"),
                         "Shift XY Only"
                       )),
                       value = TRUE
                     )),
              column(6, checkboxInput(
                "bulk_aesthetics_loop_only",
                HTML(paste(
                  icon("paint-brush", title = "If checked, only aesthetics of unlocked points will be adjusted in group.",
                       style = "margin-right: 6px;"),
                  "Aesthetics Only"
                )),
                value = FALSE
              ))
            ),
            column(
              6,
              numericInput(
                "loop_bulk_shift_x",
                label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
                value = 0,
                step = 0.1
              )
            ),
            column(
              6,
              numericInput(
                "loop_bulk_shift_y",
                label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
                value = 0,
                step = 0.1
              )
            )
          ),
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "apply_loop_changes",
                label = tagList(icon("check-circle"), HTML("&nbsp;Apply Changes")),
                class = "redo-button-main"
              ),
              style = "display: flex; justify-content: center;"
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "lock_loops",
                label = tagList(icon("lock"), HTML("&nbsp;Lock Self-loop Arrows")),
                value = FALSE,
                class = "redo-button-main"
              ),
              style = "display: flex; justify-content: center;" # Centers the button horizontally
            )
          )
        )
      ),

      # Conditional panel for SEM Diagram
      conditionalPanel(
        condition = "input.element_type == 'SEM Diagram'",
        class = "conditional-panel",
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subLavaanData",
            `aria-expanded` = "false",
            `aria-controls` = "subLavaanData",
            tags$h4(
              tagList(
                tags$span(icon("database", style = "margin-right: 8px;"),
                          title = "Produces SEM diagram with a model in lavaan syntax (without quotations) and/or data."),
                h5(HTML("<b style='font-size: 16px;'>Data and Model Specifics&nbsp</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;"),
              )
            )
          ),
          tags$div(
            id = "subLavaanData",
            class = "panel-collapse collapse",
            div(style = "display: none;", # hide
                radioButtons(
                  "data_format",
                  "Interpret as:",
                  choices = c("Data frame" = "df", "Matrix" = "matrix"),
                  selected = "df",
                  inline = TRUE
                )
            ),
            tagList(
              tags$span(
                icon("question-circle"),
                style = "cursor: help; margin-right: 6px; color: #007bff;"
              ),
              fileInput("edge_label_file", "Upload Data (Data frame CSV, Optional):",  accept = ".csv"),
              helpText("Or try sample data:"),
              selectInput("sample_data_choice", "Load Sample Dataset:",
                          choices = c("Choose..." = "",
                                      "Holzinger & Swineford" = "holzinger",
                                      "Economic Indicators" = "economic",
                                      "Growth Demo" = "growth")),
              tags$div(id = "file_input_helper", style = "display: none;"),
            ),
            fluidRow(
              column(12,
                     div(
                       class = "text-center",
                       actionButton("delete_sem_file", class = "redo-button", label = tagList(icon("trash"), "Delete CSV file"))
                     )
              )
            ),
            fluidRow(style = "display: none;",
                     column(6,
                            selectInput("ai_model", "AI Model:",
                                        choices = c("Google Gemini" = "gemini",
                                                    "OpenAI GPT" = "openai",
                                                    "Mistral AI" = "mistral",
                                                    "Anthropic Claude" = "claude",
                                                    "Ollama (Local)" = "ollama"),
                                        selected = "gemini")
                     ),
                     column(6,
                            uiOutput("ai_model_settings")
                     )
            ),
            tags$div(style = "display: none;",
                     checkboxInput("generate_lavaan_syntax_ai",
                                   tagList(tags$b(style = "font-size: 16px;", "Generate Model with AI")),
                                   value = FALSE
                     )
            ),
            conditionalPanel(
              "input.generate_lavaan_syntax_ai == true",
              shiny::wellPanel(style = "background: #f8f9fa;",
                               tags$h5(icon("wand-magic-sparkles"), "AI Generation Methods"),
                               tags$div(class = "method-section",
                                        tags$h5(icon("database"), "Method 1: Generate from Data Structure"),
                                        helpText("AI will analyze your dataset and suggest a model based on variable patterns and correlations."),
                               ),
                               fluidRow(
                                 column(6,
                                        selectInput("model_type", "Model Type:",
                                                    choices = c("CFA" = "cfa",
                                                                "Path Analysis" = "path",
                                                                "SEM" = "sem",
                                                                "Growth Curve" = "growth"),
                                                    selected = "sem")),
                                 column(6,
                                        numericInput("max_factors", "Maximum Factors:",
                                                     value = 3, min = 1, max = 10, step = 1))
                               ),
                               fluidRow(
                                 column(width = 12,
                                        textAreaInput(
                                          "additional_comments",
                                          "Additional prompts (optional)",
                                          value = "",
                                          placeholder = "Write additional prompts (under 30 words).",
                                          width = "100%",
                                          height = "80px"
                                        )
                                 )
                               ),
                               fluidRow(
                                 column(12,
                                        div(
                                          class = "text-center",
                                          actionButton("generate_lavaan_syntax", class = "redo-button01", label = tagList(icon("brain"), " Generate from Data Structure"))
                                        ))
                               ),
                               tags$hr(),
                               tags$div(class = "method-section",
                                        tags$h5(icon("comments"), "Method 2: Generate from Natural Language"),
                                        helpText("Describe your theoretical model in plain English and AI will convert it to lavaan syntax. Data will be considered if it is uploaded."),
                                        fluidRow(
                                          column(width = 12,
                                                 textAreaInput(
                                                   "natural_language_input",
                                                   "Describe your model in natural language:",
                                                   value = "",
                                                   placeholder = "e.g., 'Create a CFA model with three factors: one for anxiety items, one for depression items, and one for wellbeing items. Allow the factors to correlate.'",
                                                   width = "100%",
                                                   height = "100px"
                                                 )
                                          )
                                        ),
                                        fluidRow(
                                          column(12,
                                                 div(
                                                   class = "text-center",
                                                   actionButton("generate_lavaan_syntax_from_language", class = "redo-button01", label = tagList(icon("robot"), "Generate From Description"))
                                                 ))
                                        )
                               ),
                               shiny::wellPanel(style = "background: #f8f9fa;",
                                                tags$h5(icon("lightbulb"), "Quick Examples",
                                                        tags$a(icon("chevron-down"), "Show",
                                                               `data-toggle` = "collapse", href = "#examplesPanel",
                                                               class = "pull-right")),
                                                tags$div(id = "examplesPanel", class = "collapse",
                                                         tags$p("Try these examples in the natural language input:"),
                                                         tags$ul(
                                                           tags$li(tags$code("Create a confirmatory factor analysis with three latent factors: one for anxiety symptoms (anx1, anx2, anx3), one for depression symptoms (dep1, dep2, dep3), and one for stress symptoms (str1, str2, str3). Allow all factors to correlate with each other.")),
                                                           tags$li(tags$code("Create a structural equation model where psychological distress (measured by anxiety, depression, stress) affects academic performance (measured by gpa, test_scores, attendance), controlling for socioeconomic status (measured by income, education, neighborhood)."))
                                                         )
                                                )
                               )
              )
            ),
            # Lavaan syntax input
            uiOutput("lavaan_syntax"),
            uiOutput("model_type_selector"),
            uiOutput("efa_controls"),
            tagList(
              tags$span(
                icon("question-circle"),
                title = "Customize the sem() function call or others (e.g., cfa()). `lavaan_string` and `data` are fixed variables and must remain unchanged.",
                style = "cursor: help; margin-left: 6px; color: #007bff;"
              ),
              checkboxInput(
                "write_sem_code",
                tags$b(style = "font-size: 16px;", "Custom SEM Code"),
                value = FALSE),
              conditionalPanel(
                "input.write_sem_code == true",
                uiOutput("sem_code"),
              ),
              checkboxInput(
                "multigroup_data_upload",
                tags$b(style = "font-size: 16px;", "Multi-Group Data"),
                value = FALSE
              ),
              conditionalPanel(
                "input.multigroup_data_upload",
                helpText('To change these multi-group settings, original SEM must be removed, and re-drawn with modified settings with "Draw a SEM" button. These settings cannot be changed with "Apply Changes" button.'),
                selectInput("group_var", "Grouping Variable", choices = NULL),  # Dynamically updated
                selectInput("group_level", "Group Level", choices = NULL),
                radioButtons("invariance_level", "Invariance Level for Fitting",
                             choices = c("Configural" = "configural",
                                         "Metric" = "metric",
                                         "Scalar" = "scalar"),
                             selected = "configural"),
                checkboxInput("show_lrt_stats_multigroup",  label = tags$b("Multi-Group Model Comparisons"), value = FALSE),
                conditionalPanel(
                  "input.show_lrt_stats_multigroup",
                  verbatimTextOutput("lrt_test_multigroup")
                ),
                checkboxInput("show_intercepts", label = tags$b("Show Intercept Nodes"), value = TRUE) # show intercept nodes
              )
            )
          ),
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subSEMlayouts",
            `aria-expanded` = "false",
            `aria-controls` = "subSEMlayouts",
            tags$h4(
              tagList(
                icon("project-diagram", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>SEM Layout Settings</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subSEMlayouts",
            class = "panel-collapse collapse",
            uiOutput("sem_layout"),
            conditionalPanel(
              condition = "output.is_sempath_or_tidysem_or_grViz != true",
              checkboxInput(
                "multi_group_sem_layout",
                tags$b(style = "font-size: 16px;", "Multi-Group SEM Layout Match"),
                value = FALSE
              ),
              conditionalPanel(
                "input.multi_group_sem_layout",
                wellPanel(
                  style = "background: #f8f9fa;",
                  helpText("Select group to modify using 'Which Group to Modify' dropdown above. Match is allowed only when nodes are same."),
                  fluidRow(
                    column(
                      6,
                      selectInput(
                        "modify_sem_group_select",
                        label = tagList(
                          icon("exchange-alt", style = "margin-right: 8px;"),
                          "Modify SEM Layout of:"
                        ),
                        choices = NULL
                      )
                    ),
                    column(
                      6,
                      selectInput(
                        "modify_sem_group_match",
                        label = tagList(
                          icon("exchange-alt", style = "margin-right: 8px;"),
                          "To Match Layout of:"
                        ),
                        choices = NULL
                      )
                    )
                  )
                )
              )
            ),
            conditionalPanel(
              condition = "output.is_sempath_or_grViz != true",
              checkboxInput(
                "multi_group_sem_combine_menu",
                tags$b(style = "font-size: 16px;", "Multi-Group SEM Combine"),
                value = FALSE
              ),
              conditionalPanel(
                "input.multi_group_sem_combine_menu",
                wellPanel(
                  style = "background: #f8f9fa;",
                  helpText("Combine two SEMs into one. They can share a single multi-group model or have separate models, but the variable names of nodes should be identical."),
                  fluidRow(
                    column(
                      6,
                      selectInput(
                        "multi_group_first_combine",
                        label = tagList(
                          icon("1", style = "margin-right: 8px;"),
                          "Group 1: "
                        ),
                        choices = NULL
                      )
                    ),
                    column(
                      6,
                      selectInput(
                        "multi_group_second_combine",
                        label = tagList(
                          icon("2", style = "margin-right: 8px;"),
                          "Group 2: "
                        ),
                        choices = NULL
                      )
                    )
                  ),
                  fluidRow(column(12, textInput("sep_by", "Separate Label:",
                                                value = " | ", placeholder = "Two Group Labels Separated By: "))),
                  fluidRow(
                    column(12, align = "center",
                           actionButton(
                             "multi_group_sem_combine",
                             class = "redo-button01",
                             label = tagList(icon("link"), "Produce Combined SEM"),
                             style = "display: inline-flex; align-items: center; gap: 10px;"
                           )
                    )
                  )
                )
              )
            )
          )
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subSEMstats",
            `aria-expanded` = "false",
            `aria-controls` = "subSEMstats",
            tags$h4(
              tagList(
                icon("chart-line", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>SEM Statistics</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          conditionalPanel(
            condition = "output.is_sempath != true",
            tags$div(
              id = "subSEMstats",
              class = "panel-collapse collapse",
              uiOutput("sem_stats_label"),
              h4("Multi-Group Analysis"),
              uiOutput("highlight_free_path_multi_group"),
              conditionalPanel(
                condition = "input.highlight_free_path_multi_group",
                wellPanel(
                  style = "background: #f8f9fa;",
                  helpText(HTML('Click <b>"Apply Changes"</b> to modify aesthetics in the first (left) group. Only works when models across groups are from same multi-group model')),
                  fluidRow(
                    column(
                      6,
                      selectInput(
                        "multi_group_first_free",
                        label = tagList(
                          icon("exchange-alt", style = "margin-right: 8px;"),
                          "Compare Group: "
                        ),
                        choices = NULL
                      )
                    ),
                    column(
                      6,
                      selectInput(
                        "multi_group_second_free",
                        label = tagList(
                          icon("exchange-alt", style = "margin-right: 8px;"),
                          "And Group: "
                        ),
                        choices = NULL
                      )
                    )
                  ),
                  helpText("Select group to modify using 'Which Group to Modify' dropdown above."),
                  uiOutput("highlight_free_path_multi_group_invariance"),
                  uiOutput("highlight_free_path_multi_group_difference"),
                )
              ),
              uiOutput("highlight_multi_group"),
              conditionalPanel(
                condition = "input.highlight_multi_group",
                wellPanel(
                  style = "background: #f8f9fa;",
                  helpText(HTML('Click <b>"Apply Changes"</b> to modify aesthetics in the first (left) group. Only works when models across groups are from same multi-group model')),
                  fluidRow(
                    column(
                      6,
                      selectInput(
                        "multi_group_first",
                        label = tagList(
                          icon("exchange-alt", style = "margin-right: 8px;"),
                          "Compare Group: "
                        ),
                        choices = NULL
                      )
                    ),
                    column(
                      6,
                      selectInput(
                        "multi_group_second",
                        label = tagList(
                          icon("exchange-alt", style = "margin-right: 8px;"),
                          "And Group: "
                        ),
                        choices = NULL
                      )
                    )
                  ),
                  helpText("Select group to modify using 'Which Group to Modify' dropdown above"),
                  checkboxInput(
                    "sig_diff_output",
                    tags$b(style = "font-size: 14px;", "Show Statistics"),
                    value = FALSE
                  ),
                  helpText("Results from individual parameter Wald tests between groups (for lavaaan models) or Bayesian posterior comparison (for blavaan models). Statistical results are printed once 'Apply Changes' gets clicked with the box 'Highlight Group Differences' checked."),
                  conditionalPanel(
                    "input.sig_diff_output",
                    verbatimTextOutput("sig_diff_output")  # Dynamic output
                  ),
                  uiOutput("sig_diff_stats_options"),
                  uiOutput("highlight_multi_group_edges")
                )
              ),
              h4("Model Evaluation"),
              checkboxInput(
                "show_fit_stats",
                tags$b(style = "font-size: 16px;", "Display Fit Indices"),
                value = FALSE
              ),
              conditionalPanel(
                "input.show_fit_stats",
                wellPanel(
                  style = "background: #f8f9fa;",
                  fluidRow(
                    conditionalPanel(
                      condition = "output.is_blavaan == false",
                      column(6, checkboxInput("chisq", "χ²", value = TRUE)),
                      column(6, checkboxInput("cfi_tli", "CFI/TLI", value = TRUE)),
                      column(6, checkboxInput("rmsea", "RMSEA (90% CI)", value = TRUE)),
                      column(6, checkboxInput("srmr", "SRMR", value = FALSE))
                    ),
                    conditionalPanel(
                      condition = "output.is_blavaan == true",
                      column(6, checkboxInput("ppp", "Posterior Predictive P-value", value = TRUE)),
                      column(6, checkboxInput("dic", "DIC", value = TRUE)),
                      column(6, checkboxInput("waic", "WAIC", value = TRUE)),
                      column(6, checkboxInput("looic", "LOOIC", value = FALSE)),
                    )
                  ),
                  verbatimTextOutput("fitStatsOutput"),  # Dynamic output
                  h5("Display Options:"),
                  fluidRow(
                    column(6, numericInput("x_stats_location", "X Position:",
                                           value = 0, min = 0, max = 1, step = 1)),
                    column(6, numericInput("y_stats_location", "Y Position:",
                                           value = 0, min = 0, max = 1, step = 1))
                  ),

                  # Text styling controls
                  h5("Text Styling:"),
                  fluidRow(
                    column(6, selectInput("text_stats_font", "Font:",
                                          choices = c("sans", "serif", "mono"),
                                          selected = "sans")),
                    column(6, numericInput("text_stats_size", "Text Size:",
                                           value = 16, min = 1, max = 50))
                  ),
                  fluidRow(
                    column(6, colourpicker::colourInput("text_stats_color", "Color:",
                                                        value = "#000000")),
                    column(6, numericInput("text_stats_angle", "Angle (deg):",
                                           value = 0, min = -180, max = 180))
                  ),
                  fluidRow(
                    column(6, numericInput("text_stats_alpha", "Text Alpha:",
                                           value = 1, min = 0, max = 1, step = 0.1)),
                    column(6, selectInput("text_stats_fontface", "Fontface:",
                                          choices = c("Plain" = "plain",
                                                      "Bold" = "bold",
                                                      "Italic" = "italic",
                                                      "Bold Italic" = "bold.italic"),
                                          selected = "plain"))
                  ),
                  fluidRow(
                    column(6, selectInput("text_stats_hjust", "Horizontal Align:",
                                          choices = c("Left" = 0, "Center" = 0.5, "Right" = 1),
                                          selected = 0)),
                    column(6, selectInput("text_stats_vjust", "Vertical Align:",
                                          choices = c("Top" = 0, "Middle" = 0.5, "Bottom" = 1),
                                          selected = 0.5))
                  ),
                  fluidRow(
                    column(6, checkboxInput("apply_fill_text_stats", "Fill Color",
                                            value = FALSE)),
                    conditionalPanel(
                      condition = "input.apply_fill_text_stats",
                      column(6, colourpicker::colourInput("text_stats_fill", "Filling Color:",
                                                          value = "#FFFFFF"))
                    )
                  ),

                  # Action button to add to canvas
                  fluidRow(
                    column(12,
                           actionButton("add_fit_stats_to_canvas",
                                        class = "redo-button01",
                                        label = tagList(icon("chart-line"), "Add Fit Stats to Canvas"),
                                        style = "display: flex; margin-left: auto; margin-right: auto; align-items: center; gap: 10px;")
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.is_lavaan_any == true",
                h4("Model Modification"),
                checkboxInput(
                  "modify_params",
                  tags$b(style = "font-size: 16px;", "Interactive Parameter Modification (Fix)"),
                  value = FALSE
                ),
                conditionalPanel(
                  "input.modify_params",
                  wellPanel(
                    style = "background: #f8f9fa;",
                    helpText(HTML('Click <b>"Apply Changes"</b> to see what happens to your SEM diagram with modified parameter values in your model. Parameters with fixed values will no longer be statistically significant.')),
                    fluidRow(
                      column(6, uiOutput("param_select_ui")),  # Only the dropdown is dynamic
                      column(6,
                             numericInput("param_value", "New Unstandardized Value", value = 0)  # Static
                      )
                    ),
                    div(
                      actionButton(
                        "apply_sem_param_change",
                        label = tagList(icon("fire"), HTML("&nbsp;Modify Parameter")),
                        value = FALSE,
                        class = "redo-button-main2"
                      ),
                      style = "display: flex; justify-content: center;"),
                    div(
                      actionButton(
                        "reset_sem_model",
                        label = tagList(icon("flag"), HTML("&nbsp;Reset Model")),
                        value = FALSE,
                        class = "redo-button-main2"
                      ),
                      style = "display: flex; justify-content: center;"),
                  )),
                checkboxInput(
                  "show_lrt_stats",
                  tags$b(style = "font-size: 16px;", "Model Comparisons"),
                  value = FALSE
                ),
                conditionalPanel(
                  "input.show_lrt_stats",
                  helpText("Results from likelihood ratio test comparing the original model and modified model."),
                  verbatimTextOutput("lrt_test")  # Dynamic output
                )
              ),
            ),
          )
        ),
        #
        tags$div(
          class = "panel-group",
          # Header with toggle button
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subNodeSettings",
            `aria-expanded` = "false",
            `aria-controls` = "subNodeSettings",
            tags$h4(
              tagList(
                icon("shapes", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Node Settings</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subNodeSettings",
            class = "panel-collapse collapse",
            h4(
              tagList(
                icon("sliders-h"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Global Settings</b>")
              ),
              style = "margin-top: 0;"
            ),
            checkboxInput(
              "nonselective_modify_node",
              tagList(
                icon("shapes", style = "margin-right: 8px; color: #1262B3;"),
                tags$b(style = "font-size: 16px;", "All Nodes"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.nonselective_modify_node",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("sem_nonselective_node")
              )),
            h4(
              tagList(
                icon("mouse-pointer"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Parameter-Specific Modifications</b>")
              ),
              style = "margin-top: 20;"
            ),
            checkboxInput(
              "modify_params_node",
              tagList(
                icon("paint-brush", style = "margin-right: 8px; color: #7f404a;"),
                tags$b(style = "font-size: 16px;", "Nodes Aesthetics"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_node",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_node_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_node",
                                       class = "redo-button01",
                                       label = tagList(icon("undo"), "Reset Changes"),
                                       style = "width: 100%;"
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_node_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_node_aesthetics")
              )),
            checkboxInput(
              "modify_params_node_xy",
              tagList(
                icon("map-marker-alt", style = "margin-right: 8px; color: #0f993d;"),
                tags$b(style = "font-size: 16px;", "Node XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_node_xy",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_node_xy_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: left; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_node_xy",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: right; height: 100%;",
                                     actionButton(
                                       "clear_param_node_xy_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )

                        ),
                        uiOutput("param_node_xy"),
                        helpText("Shift position of individual node."),
              )
            ),
            checkboxInput(
              "modify_params_latent_node_xy",
              tagList(
                icon("map-marker", style = "margin-right: 8px; color: #e57717;"),
                tags$b(style = "font-size: 16px;", "Latent Group XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_latent_node_xy",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_latent_node_xy_select_ui")),
                          column(6, actionButton(
                            "reset_modify_params_latent_node_xy",
                            class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                          ))
                        ),
                        uiOutput("param_latent_node_xy"),
                        helpText("Shift position of observed nodes relative to their associated latent node."),
              )
            ),
            checkboxInput(
              "modify_params_latent_node_angle",
              tagList(
                icon("sync-alt", style = "margin-right: 8px; color: #3b82f6;"),
                tags$b(style = "font-size: 16px;", "Latent Group Orientation"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_latent_node_angle",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_latent_node_angle_select_ui")),
                          column(6, actionButton(
                            "reset_modify_params_latent_node_angle",
                            class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                          ))
                        ),
                        uiOutput("param_latent_node_angle"),
                        helpText("Shift position of latent node and its associated observed nodes."),
              )
            ),
          )
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subEdgeSettings",
            `aria-expanded` = "false",
            `aria-controls` = "subEdgeSettings",
            tags$h4(
              tagList(
                icon("arrows-alt-h", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Edge Settings</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subEdgeSettings",
            class = "panel-collapse collapse",
            h4(
              tagList(
                icon("sliders-h"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Global Settings</b>")
              ),
              style = "margin-top: 0;"
            ),
            checkboxInput(
              "nonselective_modify_edge",
              tagList(
                icon("route", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "All Edges"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.nonselective_modify_edge",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("sem_nonselective_edge")
              )),
            h4(
              tagList(
                icon("mouse-pointer"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Parameter-Specific Modifications</b>")
              ),
              style = "margin-top: 20;"
            ),
            checkboxInput(
              "modify_params_edge",
              tagList(
                icon("paint-brush", style = "margin-right: 8px; color: #7f404a;"),
                tags$b(style = "font-size: 16px;", "Edges Aesthetics"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_edge",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_edge_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_edge",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_edge_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_edge_aesthetics"),
              )),
            checkboxInput(
              "modify_params_cov_edge",
              tagList(
                icon("route", style = "margin-right: 8px; color: #cc9666;"),
                tags$b(style = "font-size: 16px;", "Edges Curvature"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_cov_edge",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_cov_edge_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_cov_edge",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_cov_edge_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ), # Dynamic dropdown
                        uiOutput("param_cov_edge")
              )
            ),
            checkboxInput(
              "modify_params_edge_xy",
              tagList(
                icon("map-marker-alt", style = "margin-right: 8px; color: #0f993d;"),
                tags$b(style = "font-size: 16px;", "Edge XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_edge_xy",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_edge_xy_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_edge_xy",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_edge_xy_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_edge_xy"),
              ))
          )
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subAnnotationSettings",
            `aria-expanded` = "false",
            `aria-controls` = "subAnnotationSettings",
            tags$h4(
              tagList(
                icon("text-width", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Annotation Settings</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subAnnotationSettings",
            class = "panel-collapse collapse",
            h4(
              tagList(
                icon("sliders-h"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Global Settings</b>")
              ),
              style = "margin-top: 0;"
            ),
            checkboxInput(
              "nonselective_modify_annotation",
              tagList(
                icon("shapes", style = "margin-right: 8px; color: #CC3D3D;"),
                tags$b(style = "font-size: 16px;", "Node Labels")
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.nonselective_modify_annotation",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("sem_nonselective_nodelabel"),
              )
            ),
            checkboxInput(
              "edge_labels_annotation",
              tagList(
                icon("route", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Edge Labels"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.edge_labels_annotation",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("sem_nonselective_edgelabel")
              )),
            h4(
              tagList(
                icon("mouse-pointer"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Parameter-Specific Modifications</b>")
              ),
              style = "margin-top: 20;"
            ),
            checkboxInput(
              "modify_params_nodelabel",
              tagList(
                icon("paint-brush", style = "margin-right: 8px; color: #7f404a;"),
                tags$b(style = "font-size: 16px;", "Node Labels Aesthetics"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_nodelabel",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_nodelabel_select_ui")),
                          column(3, div(style = "display: flex; align-items: center; height: 100%;",
                                        actionButton(
                                          "reset_modify_params_nodelabel",
                                          class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                        )
                          )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_nodelabel_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_nodelabel"),
              )),
            checkboxInput(
              "modify_params_nodelabel_xy",
              tagList(
                icon("map-marker-alt", style = "margin-right: 8px; color: #0f993d;"),
                tags$b(style = "font-size: 16px;", "Node Labels XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_nodelabel_xy",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_nodelabel_xy_select_ui")),
                          column(3, div(style = "display: flex; align-items: center; height: 100%;",
                                        actionButton(
                                          "reset_modify_params_nodelabel_xy",
                                          class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                        )
                          )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_nodelabel_xy_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_nodelabel_xy")
              )),
            checkboxInput(
              "modify_params_nodelabel_text",
              tagList(
                icon("font", style = "margin-right: 8px; color: #e57717;"),
                tags$b(style = "font-size: 16px;", "Node Labels Texts"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_nodelabel_text",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_nodelabel_text_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_nodelabel_text",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_nodelabel_text_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        fluidRow(
                          column(6,
                                 textInput("modify_params_nodelabel_nodelabel", "New Label Text:",
                                           value = "", placeholder = "Enter new label text")),
                          column(6,
                                 checkboxInput(
                                   "modify_params_nodelabel_text_math_expression",
                                   HTML(
                                     paste(
                                       icon("square-root-alt", style = "margin-right: 6px;"),
                                       "Use Math Expression"
                                     )
                                   ),
                                   value = FALSE
                                 )
                          )
                        )
              )
            ),
            checkboxInput(
              "modify_params_edgelabel",
              tagList(
                icon("route", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Edge Labels"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_edgelabel",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_edgelabel_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_edgelabel",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_edgelabel_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        uiOutput("param_edgelabel"),
              )),
            checkboxInput(
              "modify_params_edgelabel_xy",
              tagList(
                icon("map-marker-alt", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Edge Labels XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_edgelabel_xy",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_edgelabel_xy_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_edgelabel_xy",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_edgelabel_xy_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_edgelabel_xy")
              )
            ),
            checkboxInput(
              "modify_params_edgelabel_text",
              tagList(
                icon("font", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Edge Labels Text"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_edgelabel_text",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_edgelabel_text_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_edgelabel_text",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_edgelabel_text_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        fluidRow(
                          column(6,
                                 textInput("modify_params_edgelabel_edgelabel", "New Label Text:",
                                           value = "", placeholder = "Enter new label text")),
                          column(6,
                                 checkboxInput(
                                   "modify_params_edgelabel_text_math_expression",
                                   HTML(
                                     paste(
                                       icon("square-root-alt", style = "margin-right: 6px;"),
                                       "Use Math Expression"
                                     )
                                   ),
                                   value = FALSE
                                 )
                          )
                        )
              )
            )
          ),
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subLoopsSettings",
            `aria-expanded` = "false",
            `aria-controls` = "subLoopsSettings",
            tags$h4(
              tagList(
                icon("undo-alt", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Loop Arrows Settings</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subLoopsSettings",
            class = "panel-collapse collapse",
            conditionalPanel(
              condition = "output.is_grViz != true",
              uiOutput("show_residuals")
            ),
            h4(
              tagList(
                icon("sliders-h"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Global Settings</b>")
              ),
              style = "margin-top: 0;"
            ),
            checkboxInput(
              "nonselective_modify_loops",
              tagList(
                icon("arrow-rotate-right", style = "margin-right: 8px; color: #dc3545;"),
                tags$b(style = "font-size: 16px;", "All Loop Arrows")
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.nonselective_modify_loops",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("sem_nonselective_loop"),
              )
            ),
            h4(
              tagList(
                icon("mouse-pointer"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Parameter-Specific Modifications</b>")
              ),
              style = "margin-top: 20;"
            ),
            checkboxInput(
              "modify_params_loop",
              tagList(
                icon("paint-brush", style = "margin-right: 8px; color: #7f404a;"),
                tags$b(style = "font-size: 16px;", "Loop Arrow Aesthetics"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_loop",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_loop_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_loop",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_loop_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )

                        ),
                        uiOutput("param_loop_aesthetics")
              )
            ),
            checkboxInput(
              "modify_params_loop_xy",
              tagList(
                icon("map-marker-alt", style = "margin-right: 8px; color: #0f993d;"),
                tags$b(style = "font-size: 16px;", "Loop Arrow XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_loop_xy",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_loop_xy_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_loop_xy",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_loop_xy_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_loop_xy")
              )
            ),
            checkboxInput(
              "batch_loop_removal_toggle",
              tagList(
                icon("trash-alt", style = "margin-right: 8px; color: #dc3545;"),
                tags$b(style = "font-size: 16px;", "Loop Removal"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.batch_loop_removal_toggle",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(9, uiOutput("loop_removal_selector")),
                          column(3,
                                 actionButton(
                                   "clear_loop_removal_list",
                                   class = "redo-button01",
                                   label = tagList(icon("times"), "Clear"),
                                   style = "margin-top: 25px; width: 100%;"
                                 )
                          )
                        )
              )
            ),
            checkboxInput(
              "modify_params_loop_location",
              tagList(
                icon("compass", style = "margin-right: 8px; color: #ff6b35;"),
                tags$b(style = "font-size: 16px;", "Loop Arrow Location"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_loop_location",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_loop_location_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_loop_location",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_loop_location_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_loop_location")
              )
            ),
            checkboxInput(
              "modify_params_looplabel",
              tagList(
                icon("font", style = "margin-right: 8px; color: #6f42c1;"),
                tags$b(style = "font-size: 16px;", "Loop Arrow Labels"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_looplabel",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_looplabel_select_ui")),
                          column(3,
                                 div(
                                   style = "display: flex; align-items: center; height: 100%;",
                                   actionButton(
                                     "reset_modify_params_looplabel",
                                     class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                   )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_looplabel_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_looplabel")
              )
            ),
            checkboxInput(
              "modify_params_looplabel_xy",
              tagList(
                icon("map-marker", style = "margin-right: 8px; color: #20c997;"),
                tags$b(style = "font-size: 16px;", "Loop Label XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_looplabel_xy",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_looplabel_xy_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_looplabel_xy",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_looplabel_xy_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_looplabel_xy")
              )
            ),
            checkboxInput(
              "modify_params_looplabel_text",
              tagList(
                icon("font", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Loop Labels Text"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_looplabel_text",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_looplabel_text_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_looplabel_text",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_looplabel_text_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        fluidRow(
                          column(6,
                                 textInput("modify_params_looplabel_looplabel", "New Label Text:",
                                           value = "", placeholder = "Enter new label text")),
                          column(6,
                                 checkboxInput(
                                   "modify_params_looplabel_text_math_expression",
                                   HTML(
                                     paste(
                                       icon("square-root-alt", style = "margin-right: 6px;"),
                                       "Use Math Expression"
                                     )
                                   ),
                                   value = FALSE
                                 )
                          )
                        )
              )
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "generate_sem",
                label = tags$span(icon("project-diagram"), HTML("&nbsp;Draw a SEM"), title = "Click to generate the SEM graph from the lavaan model."),
                class = "redo-button-main"
              ),
              style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Centers and spaces the button and help icon
            )
          ),
          column(
            12,
            div(
              actionButton(
                "apply_changes_sem",
                label = tags$span(icon("check-circle"), HTML("&nbsp;Apply Changes"), title = "Apply the changes made to an existing (unlocked) SEM diagram (all aesthetic parameters). It is only applicable for SEM diagrams created within the app, not with CSVs from previous sessions."),
                class = "redo-button-main"
              ),
              style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Centers and spaces the button and help icon
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "lock_lavaan",
                label = tags$span(icon("lock"), HTML("&nbsp;Finalize a SEM"), title = "Finalize the SEM diagram to prevent further changes."),
                value = FALSE,
                class = "redo-button-main"
              ),
              style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Centers button and icon with spacing
            )
          )
        ),
        #),
      ),
      conditionalPanel(
        condition = "input.element_type == 'Network Diagram'",
        class = "conditional-panel",
        tagList(
          tags$span(
            icon("question-circle"),
            title = "Edge List or Adjacency Matrix file is required.",
            style = "cursor: help; margin-right: 6px; color: #007bff;"
          ),
          fileInput("network_file", "Upload Network CSV File", accept = ".csv")
        ),
        fluidRow(
          column(12,
                 div(
                   class = "text-center",
                   actionButton("delete_network_file", class = "redo-button", label = tagList(icon("trash"), "Delete CSV file"))
                 )
          )
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subnetLayouts",
            `aria-expanded` = "false",
            `aria-controls` = "subnetLayouts",
            tags$h4(
              tagList(
                icon("sitemap", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Network Layout Settings</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subnetLayouts",
            class = "panel-collapse collapse",
            uiOutput("network_layout"),
            conditionalPanel(
              condition = "output.is_qgraph != true",
              checkboxInput(
                "multi_group_network_layout",
                tags$b(style = "font-size: 16px;", "Multi-Group Network Layout Match"),
                value = FALSE
              ),
              conditionalPanel(
                "input.multi_group_network_layout",
                wellPanel(
                  style = "background: #f8f9fa;",
                  helpText("Select group to modify using 'Which Group to Modify' dropdown above"),
                  fluidRow(
                    column(
                      6,
                      selectInput(
                        "modify_network_group_select",
                        label = tagList(
                          icon("exchange-alt", style = "margin-right: 8px;"),
                          "Modify Network Layout of:"
                        ),
                        choices = NULL
                      )
                    ),
                    column(
                      6,
                      selectInput(
                        "modify_network_group_match",
                        label = tagList(
                          icon("exchange-alt", style = "margin-right: 8px;"),
                          "To Match Layout of:"
                        ),
                        choices = NULL
                      )
                    )
                  )
                )
              )
            )
          )
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subNetworkstats",
            `aria-expanded` = "false",
            `aria-controls` = "subNetworkstats",
            tags$h4(
              tagList(
                icon("chart-line", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Network Statistics</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subNetworkstats",
            class = "panel-collapse collapse",
            checkboxInput(
              "show_fit_stats_network",
              tags$b(style = "font-size: 16px;", "Display Fit Indices"),
              value = FALSE
            ),
            conditionalPanel(
              "input.show_fit_stats_network",
              wellPanel(
                style = "background: #f8f9fa;",
                fluidRow(
                  column(6, checkboxInput("net_modularity", "Modularity", value = FALSE)),
                  column(6, checkboxInput("net_clustering", "Clustering Coefficient", value = TRUE)),
                  column(6, checkboxInput("net_density", "Density", value = TRUE)),
                  column(6, checkboxInput("net_avg_path", "Average Path Length", value = FALSE))
                ),
                conditionalPanel(
                  condition = "input.net_modularity == true",
                  fluidRow(
                    column(12,
                           p("Modularity calculation requires clustering. Enable clustering below (Node Settings)")
                    )
                  )
                ),
                verbatimTextOutput("networkStatsOutput"),  # Dynamic output
                h5("Display Options:"),
                fluidRow(
                  column(6, numericInput("x_stats_location_network", "X Position:",
                                         value = 0.5, min = 0, max = 1, step = 0.05)),
                  column(6, numericInput("y_stats_location_network", "Y Position:",
                                         value = 0.9, min = 0, max = 1, step = 0.05))
                ),

                # Text styling controls
                h5("Text Styling:"),
                fluidRow(
                  column(6, selectInput("text_stats_font_network", "Font:",
                                        choices = c("sans", "serif", "mono"),
                                        selected = "sans")),
                  column(6, numericInput("text_stats_size_network", "Text Size:",
                                         value = 16, min = 1, max = 50))
                ),
                fluidRow(
                  column(6, colourpicker::colourInput("text_stats_color_network", "Color:",
                                                      value = "#000000")),
                  column(6, numericInput("text_stats_angle_network", "Angle (deg):",
                                         value = 0, min = -180, max = 180))
                ),
                fluidRow(
                  column(6, numericInput("text_stats_alpha_network", "Text Alpha:",
                                         value = 1, min = 0, max = 1, step = 0.1)),
                  column(6, selectInput("text_stats_fontface_network", "Fontface:",
                                        choices = c("Plain" = "plain",
                                                    "Bold" = "bold",
                                                    "Italic" = "italic",
                                                    "Bold Italic" = "bold.italic"),
                                        selected = "plain"))
                ),
                fluidRow(
                  column(6, selectInput("text_stats_hjust_network", "Horizontal Align:",
                                        choices = c("Left" = 0, "Center" = 0.5, "Right" = 1),
                                        selected = 0)),
                  column(6, selectInput("text_stats_vjust_network", "Vertical Align:",
                                        choices = c("Top" = 0, "Middle" = 0.5, "Bottom" = 1),
                                        selected = 0.5))
                ),
                fluidRow(
                  column(6, checkboxInput("apply_fill_text_stats_network", "Fill Color",
                                          value = FALSE)),
                  conditionalPanel(
                    condition = "input.apply_fill_text_stats_network",
                    column(6, colourpicker::colourInput("text_stats_fill_network", "Filling Color:",
                                                        value = "#FFFFFF"))
                  )
                ),
                fluidRow(
                  column(12,
                         actionButton("add_fit_stats_to_canvas_network",
                                      class = "redo-button01",
                                      label = tagList(icon("chart-line"), "Add Fit Stats to Canvas"),
                                      style = "display: flex; margin-left: auto; margin-right: auto; align-items: center; gap: 10px;")
                  )
                )
              )
            )
          )
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subnodeSettings",
            `aria-expanded` = "false",
            `aria-controls` = "subnodeSettings",
            tags$h4(
              tagList(
                icon("shapes", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Node Settings</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subnodeSettings",
            class = "panel-collapse collapse",
            h4(
              tagList(
                icon("sliders-h"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Global Settings</b>")
              ),
              style = "margin-top: 0;"
            ),
            conditionalPanel(
              condition = "output.is_qgraph",
              uiOutput("qgraph_global_node"),
            ),
            conditionalPanel(
              condition = "output.is_bipartite",
              uiOutput("change_group_node_bipartite"),
            ),
            checkboxInput(
              "nonselective_modify_node_network",
              tagList(
                icon("shapes", style = "margin-right: 8px; color: #1262B3;"),
                tags$b(style = "font-size: 16px;", "All Nodes"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.nonselective_modify_node_network",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("network_node_aesthetics")
              )),
            h4(
              tagList(
                icon("mouse-pointer"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Parameter-Specific Modifications</b>")
              ),
              style = "margin-top: 20;"
            ),
            checkboxInput(
              "modify_params_node_network",
              tagList(
                icon("paint-brush", style = "margin-right: 8px; color: #7f404a;"),
                tags$b(style = "font-size: 16px;", "Nodes Aesthetics"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_node_network",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_node_network_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_node_network",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_node_network_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        uiOutput("param_node_aesthetics_network")
              )),
            checkboxInput(
              "modify_params_node_xy_network",
              tagList(
                icon("map-marker-alt", style = "margin-right: 8px; color: #0f993d;"),
                tags$b(style = "font-size: 16px;", "Node XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_node_xy_network",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_node_xy_network_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_node_xy_network",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_node_xy_network_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        uiOutput("param_node_xy_network")
              )
            )
          )
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subedgeSettings",
            `aria-expanded` = "false",
            `aria-controls` = "subedgeSettings",
            tags$h4(
              tagList(
                icon("long-arrow-alt-up", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Edge Settings</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subedgeSettings",
            class = "panel-collapse collapse",
            h4(
              tagList(
                icon("sliders-h"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Global Settings</b>")
              ),
              style = "margin-top: 0;"
            ),
            conditionalPanel(
              condition = "output.is_qgraph",
              uiOutput("qgraph_global_edge"),
            ),
            conditionalPanel(
              condition = "output.is_bipartite",
              style = "display: none;",
              uiOutput("change_group_edge_bipartite"),
            ),
            checkboxInput(
              "nonselective_modify_edge_network",
              tagList(
                icon("paint-brush", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Edges Aesthetics"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.nonselective_modify_edge_network",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("network_edge_aesthetics")
              )),
            checkboxInput(
              "scale_edge_width",
              tagList(
                icon("arrows-left-right", style = "margin-right: 8px; color: #7b3dcc;"),
                tags$b(style = "font-size: 16px;", "Scale Edge Width by Weight"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              condition = "input.scale_edge_width",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("scale_edge_width")
              )
            ),
            checkboxInput(
              "bezier_network_edges",
              tagList(
                icon("wave-square", style = "margin-right: 8px; color: #7f5d0d;"),
                tags$b(style = "font-size: 16px;", "Curve All Edges"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              condition = "input.bezier_network_edges == true",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("bezier_network_edges")
              )
            ),
            h4(
              tagList(
                icon("mouse-pointer"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Parameter-Specific Modifications</b>")
              ),
              style = "margin-top: 20;"
            ),
            checkboxInput(
              "modify_params_edge_network",
              tagList(
                icon("paint-brush", style = "margin-right: 8px; color: #7f404a;"),
                tags$b(style = "font-size: 16px;", "Edges Aesthetics"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_edge_network",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_edge_network_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_edge_network",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_edge_network_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        uiOutput("param_edge_network")
              )
            ),
            checkboxInput(
              "modify_params_bezier_network_edges",
              tagList(
                icon("wave-square", style = "margin-right: 8px; color: #7f5d0d;"),
                tags$b(style = "font-size: 16px;", "Edges Curvature"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              condition = "input.modify_params_bezier_network_edges",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_bezier_edge_network_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_bezier_network_edges",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_bezier_edge_network_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),
                        uiOutput("param_bezier_network_edges")
              ),
              checkboxInput(
                "modify_params_edge_xy_network",
                tagList(
                  icon("map-marker-alt", style = "margin-right: 8px; color: #0f993d;"),
                  tags$b(style = "font-size: 16px;", "Edge XY Positions"),
                ),
                value = FALSE
              ),
              conditionalPanel(
                "input.modify_params_edge_xy_network",
                wellPanel(style = "background: #f8f9fa;",
                          fluidRow(column(6, uiOutput("param_edge_xy_network_select_ui")),
                                   column(3,
                                          div(style = "display: flex; align-items: center; height: 100%;",
                                              actionButton(
                                                "reset_modify_params_edge_xy_network",
                                                class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                              )
                                          )
                                   ),
                                   column(3,
                                          div(style = "display: flex; align-items: center; height: 100%;",
                                              actionButton(
                                                "clear_param_edge_xy_network_list",
                                                class = "redo-button01",
                                                label = tagList(icon("times"), "Clear"),
                                                style = "width: 100%;"
                                              )
                                          )
                                   )
                          ),
                          uiOutput("param_edge_xy_network")
                )
              )
            )
          ),
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subTextSettings",
            `aria-expanded` = "false",
            `aria-controls` = "subTextSettings",
            tags$h4(
              tagList(
                icon("text-height", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Annotation Settings</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subTextSettings",
            class = "panel-collapse collapse",
            h4(
              tagList(
                icon("sliders-h"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Global Settings</b>")
              ),
              style = "margin-top: 0;"
            ),
            conditionalPanel(
              condition = "output.is_qgraph",
              uiOutput("qgraph_global_annotation"),
            ),
            conditionalPanel(
              condition = "output.is_bipartite",
              uiOutput("change_group_annotation_bipartite"),
            ),
            checkboxInput(
              "node_labels_net",
              tagList(
                icon("project-diagram", style = "margin-right: 8px; color: #CC3D3D;"),
                tags$b(style = "font-size: 16px;", "Node Labels")
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.node_labels_net",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("node_labels_network")
              )
            ),
            checkboxInput(
              "edge_labels_net",
              tagList(
                icon("route", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Edge Labels"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.edge_labels_net",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("edge_labels_network"))
            ),
            h4(
              tagList(
                icon("mouse-pointer"),
                HTML("<b style='font-size: 16px; margin-left: 8px;'>Parameter-Specific Modifications</b>")
              ),
              style = "margin-top: 20;"
            ),
            checkboxInput(
              "modify_params_nodelabel_network",
              tagList(
                icon("paint-brush", style = "margin-right: 8px; color: #7f404a;"),
                tags$b(style = "font-size: 16px;", "Node Labels Aesthetics"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_nodelabel_network",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_nodelabel_network_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_nodelabel_network",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_nodelabel_network_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        uiOutput("param_nodelabel_network")
              )
            ),
            checkboxInput(
              "modify_params_nodelabel_xy_network",
              tagList(
                icon("map-marker-alt", style = "margin-right: 8px; color: #0f993d;"),
                tags$b(style = "font-size: 16px;", "Node Labels XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_nodelabel_xy_network",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(column(6, uiOutput("param_nodelabel_xy_network_select_ui")),
                                 column(3,
                                        div(style = "display: flex; align-items: center; height: 100%;",
                                            actionButton(
                                              "reset_modify_params_nodelabel_xy_network",
                                              class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                            )
                                        )
                                 ),
                                 column(3,
                                        div(style = "display: flex; align-items: center; height: 100%;",
                                            actionButton(
                                              "clear_param_nodelabel_xy_network_list",
                                              class = "redo-button01",
                                              label = tagList(icon("times"), "Clear"),
                                              style = "width: 100%;"
                                            )
                                        )
                                 )
                        ),
                        uiOutput("param_nodelabel_xy_network")
              )
            ),
            checkboxInput(
              "modify_params_nodelabel_text_network",
              tagList(
                icon("font", style = "margin-right: 8px; color: #e57717;"),
                tags$b(style = "font-size: 16px;", "Node Labels Texts"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_nodelabel_text_network",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_nodelabel_text_network_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_nodelabel_text_network",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_nodelabel_text_network_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        fluidRow(column(6,
                                        textInput("modify_params_nodelabel_network_nodelabel", "New Label Text:",
                                                  value = "", placeholder = "Enter new label text")),
                                 column(6,
                                        checkboxInput(
                                          "modify_params_nodelabel_text_network_math_expression",
                                          HTML(
                                            paste(
                                              icon("square-root-alt", style = "margin-right: 6px;"),
                                              "Use Math Expression"
                                            )
                                          ),
                                          value = FALSE
                                        )
                                 ))
              )),
            checkboxInput(
              "modify_params_edgelabel_network",
              tagList(
                icon("route", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Edge Labels"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_edgelabel_network",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(column(6, uiOutput("param_edgelabel_network_select_ui")),
                                 column(3,
                                        div(style = "display: flex; align-items: center; height: 100%;",
                                            actionButton(
                                              "reset_modify_params_edgelabel_network",
                                              class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                            )
                                        )
                                 ),
                                 column(3,
                                        div(style = "display: flex; align-items: center; height: 100%;",
                                            actionButton(
                                              "clear_param_edgelabel_network_list",
                                              class = "redo-button01",
                                              label = tagList(icon("times"), "Clear"),
                                              style = "width: 100%;"
                                            )
                                        )
                                 )
                        ),  # Dynamic dropdown
                        uiOutput("param_edgelabel_network")
              )
            ),
            checkboxInput(
              "modify_params_edgelabel_xy_network",
              tagList(
                icon("map-marker-alt", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Edge Labels XY Positions"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_edgelabel_xy_network",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(column(6, uiOutput("param_edgelabel_xy_network_select_ui")),
                                 column(3,
                                        div(style = "display: flex; align-items: center; height: 100%;",
                                            actionButton(
                                              "reset_modify_params_edgelabel_xy_network",
                                              class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                            )
                                        )
                                 ),
                                 column(3,
                                        div(style = "display: flex; align-items: center; height: 100%;",
                                            actionButton(
                                              "clear_param_edgelabel_xy_network_list",
                                              class = "redo-button01",
                                              label = tagList(icon("times"), "Clear"),
                                              style = "width: 100%;"
                                            )
                                        )
                                 )),
                        uiOutput("param_edgelabel_xy_network")
              )
            ),
            checkboxInput(
              "modify_params_edgelabel_text_network",
              tagList(
                icon("font", style = "margin-right: 8px; color: #5b4080;"),
                tags$b(style = "font-size: 16px;", "Edge Labels Text"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.modify_params_edgelabel_text_network",
              wellPanel(style = "background: #f8f9fa;",
                        fluidRow(
                          column(6, uiOutput("param_edgelabel_text_network_select_ui")),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "reset_modify_params_edgelabel_text_network",
                                       class = "redo-button01", label = tagList(icon("undo"), "Reset Changes")
                                     )
                                 )
                          ),
                          column(3,
                                 div(style = "display: flex; align-items: center; height: 100%;",
                                     actionButton(
                                       "clear_param_edgelabel_text_network_list",
                                       class = "redo-button01",
                                       label = tagList(icon("times"), "Clear"),
                                       style = "width: 100%;"
                                     )
                                 )
                          )
                        ),  # Dynamic dropdown
                        fluidRow(
                          column(6,
                                 textInput("modify_params_edgelabel_edgelabel_network", "New Label Text:",
                                           value = "", placeholder = "Enter new label text")),
                          column(6,
                                 checkboxInput(
                                   "modify_params_edgelabel_text_network_math_expression",
                                   HTML(
                                     paste(
                                       icon("square-root-alt", style = "margin-right: 6px;"),
                                       "Use Math Expression"
                                     )
                                   ),
                                   value = FALSE
                                 )
                          )
                        )
              )
            ),
          ),
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "generate_network",
                label = tags$span(icon("project-diagram"), HTML("&nbsp;Draw a Network"), title = "Click to generate the network diagram from a CSV file."),
                class = "redo-button-main"
              ),
              style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Centered alignment and spacing
            )
          ),
          column(
            12,
            div(
              actionButton(
                "apply_changes_network",
                label = tags$span(icon("check-circle"), HTML("&nbsp;Apply Changes"), title = "Apply the changes made to an existing (unlocked) network diagram (all aesthetic parameters)."),
                class = "redo-button-main"
              ),
              style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Centered alignment and spacing
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "lock_network",
                label = tags$span(icon("lock"), HTML("&nbsp;Finalize a Network"), title = "Finalize the network diagram to prevent further changes."),
                value = FALSE,
                class = "redo-button-main"
              ),
              style = "display: flex; align-items: center; justify-content: center; gap: 10px;" # Ensures proper alignment and spacing
            )
          )
        ),
        #)
      ),
      conditionalPanel(
        condition = "input.element_type == 'Aesthetic Grouping'",
        class = "conditional-panel",
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subGroupingLayout",
            `aria-expanded` = "false",
            `aria-controls` = "subGroupingLayout",
            tags$h4(
              tagList(
                icon("palette", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Group Aesthetics</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subGroupingLayout",
            class = "panel-collapse collapse",
            checkboxInput(
              "group_aesthetics_point_only",
              tagList(
                icon("circle", style = "margin-right: 8px; color: #FF5722;"),
                tags$b(style = "font-size: 16px;", "Points"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              "input.group_aesthetics_point_only",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("group_aesthetics_point"),
                        tags$div(
                          style = "position: relative;",
                          #style = "position: absolute; bottom: 10px; right: 10px; font-size: 12px; color: #007bff;",
                          tags$span(
                            icon("info-circle", style = "margin-right: 6px;"),
                            "These inputs support aesthetic grouping for unlocked points."
                          )
                        )
              )
            ),
            checkboxInput(
              "group_aesthetics_line_only",
              tagList(
                icon("minus", style = "margin-right: 8px; color: #2196F3;"),
                tags$b(style = "font-size: 16px;", "Lines"),
              ),
              value = FALSE),
            conditionalPanel(
              "input.group_aesthetics_line_only",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("group_aesthetics_line"),
                        tags$div(
                          style = "position: relative;",
                          tags$span(
                            icon("info-circle", style = "margin-right: 6px;"),
                            "These inputs support aesthetic grouping for unlocked lines."
                          )
                        )
              )
            ),
            checkboxInput(
              "group_aesthetics_annotation_only",
              tagList(
                icon("font", style = "margin-right: 8px; color: #9C27B0;"),
                tags$b(style = "font-size: 16px;", "Annotations"),
              ),
              value = FALSE),
            conditionalPanel(
              "input.group_aesthetics_annotation_only",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("group_aesthetics_annotation"),
                        tags$div(
                          style = "position: relative;",
                          #style = "position: absolute; bottom: 10px; right: 10px; font-size: 12px; color: #007bff;",
                          tags$span(
                            icon("info-circle", style = "margin-right: 6px;"),
                            "These inputs support aesthetic grouping for unlocked annotations."
                          )
                        )
              )
            ),
            checkboxInput(
              "group_aesthetics_loop_only",
              tagList(
                icon("redo-alt", style = "margin-right: 8px; color: #4CAF50;"),
                tags$b(style = "font-size: 16px;", "Self-loop Arrows"),
              ),
              value = FALSE),
            conditionalPanel(
              "input.group_aesthetics_loop_only",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("group_aesthetics_loop"),
                        tags$div(
                          style = "position: relative;",
                          #style = "position: absolute; bottom: 10px; right: 10px; font-size: 12px; color: #007bff;",
                          tags$span(
                            icon("info-circle", style = "margin-right: 6px;"),
                            "These inputs support aesthetic grouping for unlocked self-loop arrows."
                          )
                        )
              )
            )
          )
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subGroupingFilter",
            `aria-expanded` = "false",
            `aria-controls` = "subGroupingFilter",
            tags$h4(
              tagList(
                icon("tags", style = "margin-right: 8px;"),
                h5(HTML("<b style='font-size: 16px;'>Group Label Edit</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subGroupingFilter",
            class = "panel-collapse collapse",
            checkboxInput(
              "show_group_labels",
              tagList(
                icon("tags", style = "margin-right: 8px; color: #2E86AB;"),
                tags$b(style = "font-size: 16px;", "Show Group Labels")
              ),
              value = FALSE
            ),
            conditionalPanel(
              condition = "input.show_group_labels",
              wellPanel(
                style = "background: #f8f9fa;",
                fluidRow(
                  column(6,
                         selectInput(
                           "group_label_position",
                           tagList(
                             icon("compass", style = "margin-right: 8px;"),
                             "Label Position:"
                           ),
                           choices = c(
                             "Top Center" = "top",
                             "Bottom Center" = "bottom",
                             "Left Center" = "left",
                             "Right Center" = "right",
                             "Top Left" = "top_left",
                             "Top Right" = "top_right",
                             "Bottom Left" = "bottom_left",
                             "Bottom Right" = "bottom_right"
                           ),
                           selected = "top"
                         )
                  ),
                  column(6,
                         selectInput(
                           "group_label_alignment",
                           label = tagList(icon("align-center", style = "margin-right: 8px;"), "Alignment: "),
                           choices = c(
                             "Relative to each group" = "relative",
                             "Fixed X coordinate" = "fixed_x",
                             "Fixed Y coordinate" = "fixed_y",
                             "Fixed grid" = "fixed_grid"
                           ),
                           selected = "relative"
                         )
                  )
                ),
                fluidRow(
                  column(6,
                         conditionalPanel(
                           condition = "input.group_label_alignment == 'fixed_x' || input.group_label_alignment == 'fixed_grid'",
                           numericInput(
                             "group_label_fixed_x",
                             label = tagList(icon("arrows-alt-h", style = "margin-right: 8px;"), "Fixed X Position: "),
                             value = 0,
                             step = 0.1
                           )
                         )
                  ),
                  column(6,
                         conditionalPanel(
                           condition = "input.group_label_alignment == 'fixed_y' || input.group_label_alignment == 'fixed_grid'",
                           numericInput(
                             "group_label_fixed_y",
                             label = tagList(icon("arrows-alt-v", style = "margin-right: 8px;"), "Fixed Y Position: "),
                             value = 0,
                             step = 0.1
                           )
                         )
                  )
                ),
                fluidRow(
                  column(6, numericInput("group_label_offset", "Label Offset:", value = 5, min = 0, step = 0.1)),
                  column(6, numericInput("group_label_angle", "Label Angle:", value = 0, min = -180, max = 180)),
                ),
                fluidRow(
                  column(6, colourInput("group_label_color", "Label Color:", value = "#000000")),
                  column(6, numericInput("group_label_size", "Label Size:", value = 25, min = 1, max = 20, step = 0.5)),
                ),
                fluidRow(
                  column(6, selectInput("group_label_fontface", "Font Face:",
                                        choices = c("plain", "bold", "italic"),
                                        selected = "bold")),
                  column(6, selectInput("group_label_font", "Font:",
                                        choices = c("sans", "serif", "mono"),
                                        selected = "sans"
                  ))
                ),
                fluidRow(
                  column(12,
                         div(
                           actionButton(
                             "update_group_labels",
                             class = "redo-button01",
                             label = tagList(icon("sync"), HTML("&nbsp;Show Group Labels"))
                           ),
                           style = "display: flex; justify-content: center;"
                         ))
                )
              )
            ),
            checkboxInput(
              "rename_group_labels",
              tagList(
                icon("users", style = "margin-right: 8px; color: #0F993D;"),
                tags$b(style = "font-size: 16px;", "Rename Group Labels")
              ),
              value = FALSE
            ),
            conditionalPanel(
              condition = "input.rename_group_labels",
              wellPanel(style = "background: #f8f9fa;",
                        helpText("Renaming applies to all elements of a selected group."),
                        fluidRow(
                          column(
                            6,
                            selectInput(
                              "rename_group_select",
                              label = tagList(
                                icon("folder-open", style = "margin-right: 8px;"),
                                "Rename Group Labels?"
                              ),
                              choices = NULL
                            )
                          ),
                          column(6,
                                 textInput("new_group_name",
                                           label = tagList(
                                             icon("pencil-alt", style = "margin-right: 8px;"),
                                             "New Group Name"
                                           ),
                                           value = "1",
                                           placeholder = "To all elements of a selected group")
                          )
                        ),
                        fluidRow(
                          column(
                            12,
                            div(
                              style = "margin-top: 24px;",
                              actionButton(
                                "rename_group",
                                class = "redo-button01",
                                label = tags$span(icon("sync-alt"), HTML("&nbsp;Rename Group"),
                                                  title = "Rename a group's name"),
                              ),
                              style = "display: flex; align-items: center; justify-content: center; gap: 10px;"
                            )
                          )
                        )
              )
            ),
            checkboxInput(
              "modify_group_labels",
              tagList(
                icon("tags", style = "margin-right: 8px; color: #A23B72;"),
                tags$b(style = "font-size: 16px;", "Modify Group Labels (By Rows)")
              ),
              value = FALSE
            ),
            conditionalPanel(
              condition = "input.modify_group_labels",
              wellPanel(style = "background: #f8f9fa;",
                        helpText("Select row(s) in the output tables on the right. Only applies to rows where lavaan, network, and locked columns are FALSE."),
                        fluidRow(
                          column(
                            6,
                            selectInput(
                              "modify_group_select",
                              label = tagList(
                                icon("exchange-alt", style = "margin-right: 8px;"),
                                "Reassign Group Labels?"
                              ),
                              choices = NULL
                            )
                          ),
                          column(
                            6,
                            div(
                              style = "margin-top: 24px;",
                              actionButton(
                                "modify_group",
                                class = "redo-button01",
                                label = tags$span(icon("random"), HTML("&nbsp;Reassign Labels"),
                                                  title = "Reassign group label of selected existing elements from output tables to an existing group label"),
                              ),
                              style = "display: flex; align-items: right; justify-content: right; gap: 10px;"
                            )
                          )
                        ),
                        fluidRow(
                          column(6,
                                 textInput("new_group_select",
                                           label = tagList(
                                             icon("table", style = "margin-right: 8px;"),
                                             "Assign New Group Labels"
                                           ),
                                           value = "1",
                                           placeholder = "To newly added element(s)")
                          ),
                          column(
                            6,
                            div(
                              style = "margin-top: 24px;",
                              actionButton(
                                "new_group",
                                class = "redo-button01",
                                label = tags$span(icon("table"), HTML("&nbsp;Assign New Labels"),
                                                  title = "Create new group labels for selected existing elements from output tables"),
                              ),
                              style = "display: flex; align-items: right; justify-content: right; gap: 10px;"
                            )
                          )
                        ),
                        fluidRow(
                          column(12,
                                 div(
                                   class = "text-center",
                                   actionButton("clear_group", class = "redo-button01", label = tagList(icon("table"), "Clear Group"))
                                 )
                          )
                        ),
              )
            ),
            checkboxInput(
              "delete_group_labels",
              tagList(
                icon("trash-alt", style = "margin-right: 8px; color: #F18F01;"),
                tags$b(style = "font-size: 16px;", "Delete Group Labels")
              ),
              value = FALSE
            ),
            conditionalPanel(
              condition = "input.delete_group_labels",
              wellPanel(style = "background: #f8f9fa;",
                        helpText("Delete elements associated with the selected group from the menu above"),
                        fluidRow(
                          column(6,
                                 selectInput(
                                   "delete_group_select",
                                   label = tagList(
                                     icon("trash-alt", style = "margin-right: 8px;"),
                                     "Delete Selected Group"
                                   ),
                                   choices = NULL
                                 )
                          ),
                          column(
                            6,
                            div(
                              style = "margin-top: 24px;",
                              actionButton(
                                "delete_group",
                                class = "redo-button01",
                                label = tags$span(icon("mouse-pointer"), HTML("&nbsp;Delete Group"),
                                                  title = "Delete elements with specific group label"),
                              ),
                              style = "display: flex; align-items: right; justify-content: right; gap: 10px;"
                            )
                          )
                        ),
                        helpText("Lock elements associated with the selected group from the menu above"),
                        fluidRow(
                          column(6,
                                 selectInput(
                                   "lock_group_select",
                                   label = tagList(
                                     icon("lock", style = "margin-right: 8px;"),
                                     "Lock Selected Group"
                                   ),
                                   choices = NULL
                                 )
                          ),
                          column(
                            6,
                            div(
                              style = "margin-top: 24px;",
                              actionButton(
                                "lock_group",
                                class = "redo-button01",
                                label = tags$span(icon("mouse-pointer"), HTML("&nbsp;Lock Group"),
                                                  title = "Lock elements with specific group label"),
                              ),
                              style = "display: flex; align-items: right; justify-content: right; gap: 10px;"
                            )
                          )
                        )
              )
            )
          )
        ),
        tags$div(
          class = "panel-group",
          tags$div(
            class = "toggle-button collapsed",
            `data-toggle` = "collapse",
            `data-target` = "#subGroupPosition",
            `aria-expanded` = "false",
            `aria-controls` = "subGroupPosition",
            tags$h4(
              tagList(
                icon("map-marker-alt", style = "margin-right: 8px;"),  # Unique icon for Group Position
                h5(HTML("<b style='font-size: 16px;'>Group Position</b>")),
                tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
              )
            )
          ),
          tags$div(
            id = "subGroupPosition",
            class = "panel-collapse collapse",
            checkboxInput(
              "group_shift_xy",
              tagList(
                icon("arrows-alt", style = "margin-right: 8px; color: #009688;"),
                tags$b(style = "font-size: 16px;", "Position XY Shift"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              condition = "input.group_shift_xy == true",
              wellPanel(style = "background: #f8f9fa;",
                        uiOutput("group_shift_xy"),
              ),
            ),
            # checkboxInput(
            #   "group_shift_angle",
            #   tagList(
            #     icon("sync", style = "margin-right: 8px; color: #3F51B5;"),
            #     tags$b(style = "font-size: 16px;", "Group Orientation"),
            #   ),
            #   value = FALSE
            # ),
            # conditionalPanel(
            #   "input.group_shift_angle",
            #   wellPanel(style = "background: #f8f9fa;",
            #             uiOutput("group_shift_angle")
            #   )
            # ),
            checkboxInput(
              "align_groups",
              tagList(
                icon("align-center", style = "margin-right: 8px; color: #00BCD4;"),
                tags$b(style = "font-size: 16px;", "Align Groups"),
              ),
              value = FALSE
            ),
            conditionalPanel(
              condition = "input.align_groups",
              wellPanel(style = "background: #f8f9fa;",
                        selectInput("align_groups_method", "Alignment Method",
                                    choices = c("Align Horizontal Centers" = "horizontal_center",
                                                "Align Vertical Centers" = "vertical_center",
                                                "Align Left Edges" = "left",
                                                "Align Right Edges" = "right",
                                                "Align Top Edges" = "top",
                                                "Align Bottom Edges" = "bottom")),
                        selectizeInput("groups_to_align", "Groups to Align",
                                       choices = NULL, multiple = TRUE,
                                       options = list(placeholder = 'Select groups to align')),
                        fluidRow(
                          column(12,
                                 div(
                                   class = "text-center",
                                   actionButton("align_groups_button", class = "redo-button01", label = tagList(icon("arrows-alt-h"), "Apply Alignment"))
                                 )
                          )
                        ),
              )
            )
          )
        ),
        fluidRow(
          column(
            12,
            div(
              actionButton(
                "apply_group_changes",
                label = tagList(icon("check-circle"), HTML("&nbsp;Apply Changes")),
                class = "redo-button-main"
              ),
              style = "display: flex; justify-content: center;"
            )
          )
        )
      ),
      fluidRow(
        column(
          12,
          div(
            class = "text-center",
            actionButton("undo_button", class = "redo-button", label = tagList(icon("undo"), "Undo")),
            actionButton("redo_button", class = "redo-button", label = tagList(icon("redo"), "Redo")),
            actionButton("delete_everything", class = "redo-button", label = tagList(icon("trash"), "Clear")) # New Clear button
          )
        )
      ),
      div(style = "margin-top: 10px;"),
      # Download CSV dropdown menu
      tags$div(
        class = "panel-group",
        style = "margin: 0; padding: 0;",
        tags$div(tags$div(
          class = "toggle-button collapsed",
          `data-toggle` = "collapse",
          `data-target` = "#exportCSV",
          `aria-expanded` = "false",
          `aria-controls` = "exportCSV",
          tags$h4(
            tagList(
              icon("image", style = "margin-right: 8px;"),
              h5(HTML("<b style='font-size: 16px;'>Export or Import CSV Outputs / Save Images </b>")),
              tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
            )
          )),
          tags$div(
            id = "exportCSV",
            class = "panel-collapse collapse",
            selectInput(
              "csv_type",
              "Choose CSV to Download:",
              choices = c("Points CSV", "Lines CSV", "Annotations CSV", "Self-loop Arrows CSV")
            ),
            downloadButton(
              "download_selected_csv",
              "Download Selected CSV",
              class = "redo-button"
            ),
            helpText("Download visualization output CSVs without metadata."),
            div(style = "margin-top: 10px;"),
            selectInput(
              "export_format",
              "Choose Export Format:",
              choices = c("PNG", "JPEG", "PDF", "SVG")
            ),
            div(
              style = "margin-top: 10px;",
              fluidRow(
                column(
                  6,
                  checkboxInput(
                    "use_x_range",
                    HTML("Specify X Range <i class='fa fa-question-circle' style='color: #007bff; cursor: pointer;' title='Define the X-axis range to customize the view of your plot.'></i>"),
                    value = FALSE
                  )
                ),
                column(
                  6,
                  checkboxInput(
                    "fixed_aspect_ratio",
                    HTML("Fixed Aspect Ratio (1:1) <i class='fa fa-question-circle' style='color: #007bff; cursor: pointer;' title='Maintain a 1:1 aspect ratio for the plot.'></i>"),
                    value = FALSE
                  )
                )
              ),
              conditionalPanel(
                condition = "input.use_x_range == true",
                fluidRow(
                  column(6, numericInput("x_range_min", "X Range Min:", value = NA, step = 1)),
                  column(6, numericInput("x_range_max", "X Range Max:", value = NA, step = 1))
                )
              ),
              checkboxInput(
                "use_y_range",
                HTML("Specify Y Range <i class='fa fa-question-circle' style='color: #007bff; cursor: pointer;' title='Define the Y-axis range to customize the view of your plot.'></i>"),
                value = FALSE
              ),
              conditionalPanel(
                condition = "input.use_y_range == true",
                fluidRow(
                  column(6, numericInput("y_range_min", "Y Range Min:", value = NA, step = 1)),
                  column(6, numericInput("y_range_max", "Y Range Max:", value = NA, step = 1))
                )
              )
            ),
            downloadButton("download_plot", "Save the Figure", class = "redo-button"),
            textOutput("instruction"),
            helpText("Load visualization output CSVs from previous sessions without metadata."),
            fileInput("points_file", "Upload Points CSV"),
            fileInput("lines_file", "Upload Lines CSV"),
            fileInput("annotations_file", "Upload Annotations CSV"),
            fileInput("self_loop_file", "Upload Self Loop Arrows CSV"),
          ),
        )
      ),
      tags$div(
        class = "panel-group",
        style = "margin: 0; padding: 0;",
        tags$div(tags$div(
          class = "toggle-button collapsed",
          `data-toggle` = "collapse",
          `data-target` = "#metadata",
          `aria-expanded` = "false",
          `aria-controls` = "metadta",
          tags$h4(
            tagList(
              icon("camera", style = "margin-right: 8px;"),
              h5(HTML("<b style='font-size: 16px;'>Workflow Metadata & Reproducibility</b>")),
              tags$i(class = "fas fa-chevron-down", style = "margin-left: auto;")
            )
          )),
          tags$div(
            id = "metadata",
            class = "panel-collapse collapse",
            h4("Save Current Work"),
            actionButton("capture_workflow", class = "redo-button", label = tagList(icon("camera-retro"), "Capture Workflow")),
            helpText("Captures everything: all groups, models, data files, and visual elements."),
            div(style = "margin-top: 10px;"),
            downloadButton("export_workflow", "Download Workflow", class = "redo-button", icon = icon("download")),
            radioButtons("undo_history_option",
                         label = NULL,
                         choices = list(
                           "None (current state only)" = 0,
                           "Last 10 steps" = 10,
                           "Last 50 steps" = 50,
                           "Complete history" = -1
                         ),
                         selected = -1,
                         inline = FALSE),
            helpText("Larger history = better reproducibility but larger file size."),
            hr(),
            h4("Continue Previous Work"),
            fileInput("upload_workflow", "Upload Workflow to Continue",
                      accept = c(".rds"),
                      buttonLabel = "Browse...",
                      placeholder = "Select .rds workflow file"),
            actionButton("load_workflow", class = "redo-button",  label = tagList(icon("upload"), "Load Workflow")),
            hr(),
            h4("Metadata Status"),
            verbatimTextOutput("workflow_summary"),
            verbatimTextOutput("upload_status")
          )
        )
      ),
    ),

    # Main panel for plot and data tables
    mainPanel(
      div(
        style = "border: 2px solid dimgray; padding: 5px;",
        plotOutput("plot", hover = hoverOpts(id = "plot_hover"), height = "700px", width = "100%")
      ),
      textOutput("hover_info"),
      br(),
      fluidRow(
        column(12, h4(
          tagList(
            icon("table"), # Replace with a suitable icon, e.g., "table"
            HTML("&nbsp;"), # Add space between the icon and text
            "Output Tables"
          )
        )),

        # Scrollable container for all tables
        fluidRow(
          column(
            12,
            tabsetPanel(
              # Points Table Tab
              tabPanel(
                title = tagList(icon("plus-circle"), "Points Table"),
                div(
                  class = "scrollable-tables",
                  fluidRow(
                    column(
                      4,
                      actionButton(
                        "delete_selected_point",
                        label = tagList(icon("trash-alt"), HTML("&nbsp;&nbsp;Delete Selected Point(s)")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "unlock_selected_point",
                        label = tagList(icon("unlock"), HTML("&nbsp;&nbsp;Unlock Selected Point(s)")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "lock_selected_point",
                        label = tagList(icon("lock"), HTML("&nbsp;&nbsp;Lock Selected Point(s)")),
                        class = "redo-button0"
                      )
                    ),
                  ),
                  fluidRow(
                    column(
                      4,
                      actionButton(
                        "delete_all_points",
                        label = tagList(icon("trash"), HTML("&nbsp;&nbsp;Delete All Points")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "unlock_points",
                        label = tagList(icon("unlock"), HTML("&nbsp;&nbsp;Unlock All Points")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "lock_points",
                        label = tagList(icon("lock"), HTML("&nbsp;&nbsp;Lock All Points")),
                        class = "redo-button0"
                      )
                    )
                  ),
                  tags$div(style = "height: 7.5px;"),
                  DTOutput("data_table")
                )
              ),
              # Lines Table Tab
              tabPanel(
                title = tagList(icon("arrows-alt-h"), "Lines Table"),
                div(
                  class = "scrollable-tables",
                  fluidRow(
                    column(
                      4,
                      actionButton(
                        "delete_selected_line",
                        label = tagList(icon("trash-alt"), HTML("&nbsp;&nbsp;Delete Selected Line(s)")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "unlock_selected_lines",
                        label = tagList(icon("unlock"), HTML("&nbsp;&nbsp;Unlock Selected Line(s)")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "lock_selected_lines",
                        label = tagList(icon("lock"), HTML("&nbsp;&nbsp;Lock Selected Line(s)")),
                        class = "redo-button0"
                      )
                    )
                  ),
                  fluidRow(
                    column(
                      4,
                      actionButton(
                        "delete_all_lines",
                        label = tagList(icon("trash"), HTML("&nbsp;&nbsp;Delete All Lines")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "unlock_lines_button",
                        label = tagList(icon("unlock"), HTML("&nbsp;&nbsp;Unlock All Lines")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "lock_lines_button",
                        label = tagList(icon("lock"), HTML("&nbsp;&nbsp;Lock All Lines")),
                        class = "redo-button0"
                      )
                    )
                  ),
                  tags$div(style = "height: 7.5px;"),
                  DTOutput("line_table")
                )
              ),
              # Annotations Table Tab
              tabPanel(
                title = tagList(icon("pencil-alt"), "Annotations Table"),
                div(
                  class = "scrollable-tables",
                  fluidRow(
                    column(
                      4,
                      actionButton(
                        "delete_selected_annotation",
                        label = tagList(icon("trash-alt"), HTML("&nbsp;&nbsp;Delete Selected Annotation(s)")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "unlock_selected_annotation",
                        label = tagList(icon("unlock"), HTML("&nbsp;&nbsp;Unlock Selected Annotation(s)")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "lock_selected_annotation",
                        label = tagList(icon("lock"), HTML("&nbsp;&nbsp;Lock Selected Annotation(s)")),
                        class = "redo-button0"
                      )
                    )
                  ),
                  fluidRow(
                    column(
                      4,
                      actionButton(
                        "delete_all_annotations",
                        label = tagList(icon("trash"), HTML("&nbsp;&nbsp;Delete All Annotations")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "unlock_annotations_button",
                        label = tagList(icon("unlock"), HTML("&nbsp;&nbsp;Unlock All Annotations")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "lock_annotations_button",
                        label = tagList(icon("lock"), HTML("&nbsp;&nbsp;Lock All Annotations")),
                        class = "redo-button0"
                      )
                    )
                  ),
                  tags$div(style = "height: 7.5px;"),
                  DTOutput("annotation_table")
                )
              ),
              # Self-loop Arrows Table Tab
              tabPanel(
                title = tagList(tags$i(class = "fa fa-redo", style = "transform: rotate(135deg);"), "Self-loop Arrows Table"),
                div(
                  class = "scrollable-tables",
                  fluidRow(
                    column(
                      4,
                      actionButton(
                        "delete_selected_loop",
                        label = tagList(icon("trash-alt"), HTML("&nbsp;&nbsp;Delete Selected Self-loop Arrow(s)")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "unlock_selected_loop",
                        label = tagList(icon("unlock"), HTML("&nbsp;&nbsp;Unlock Selected Self-loop Arrow(s)")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "lock_selected_loop",
                        label = tagList(icon("lock"), HTML("&nbsp;&nbsp;Lock Selected Self-loop Arrow(s)")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "delete_all_loops",
                        label = tagList(icon("trash"), HTML("&nbsp;&nbsp;Delete All Self-loop Arrows")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "unlock_all_loops",
                        label = tagList(icon("unlock"), HTML("&nbsp;&nbsp;Unlock All Self-loop Arrows")),
                        class = "redo-button0"
                      )
                    ),
                    column(
                      4,
                      actionButton(
                        "lock_all_loops",
                        label = tagList(icon("lock"), HTML("&nbsp;&nbsp;Lock All Self-loop Arrows")),
                        class = "redo-button0"
                      )
                    )
                  ),
                  tags$div(style = "height: 7.5px;"),
                  DTOutput("loop_table")
                )
              )
            )
          )
        )
      ),
      fluidRow(
        column(12, textOutput("axis_info")) # hover -> XY coord
      )
    )
  )
)

server <- function(input, output, session) {
  options(warn = -1)
  # For undo/redo history

  values <- reactiveValues(
    points = data.frame(
      x = numeric(), y = numeric(), shape = character(), color = character(), size = numeric(),
      border_color = character(), border_width = numeric(), alpha = numeric(), width_height_ratio = numeric(),
      orientation = numeric(), lavaan = logical(), network = logical(), locked = logical(), group = character(), stringsAsFactors = FALSE
    ),
    lines = data.frame(
      x_start = numeric(), y_start = numeric(), x_end = numeric(), y_end = numeric(),
      ctrl_x = numeric(), ctrl_y = numeric(), ctrl_x2 = numeric(), ctrl_y2 = numeric(),
      curvature_magnitude = numeric(), rotate_curvature = logical(), curvature_asymmetry = numeric(),
      type = character(), color = character(), end_color = character(), color_type = character(),
      gradient_position = numeric(), width = numeric(), alpha = numeric(), arrow = logical(), arrow_type = character(),
      arrow_size = numeric(), two_way = logical(), lavaan = logical(), network = logical(), line_style = character(), locked = logical(),
      group = character(), stringsAsFactors = FALSE
    ),
    annotations = data.frame(
      text = character(), x = numeric(), y = numeric(), font = character(), size = numeric(), color = character(), fill = character(), angle = numeric(), alpha = numeric(),
      fontface = character(), math_expression = logical(), hjust = numeric(), vjust = numeric(), lavaan = logical(), network = logical(), locked = logical(), group_label = logical(), loop_label = logical(), group = character(),
      stringsAsFactors = FALSE
    ),
    loops = data.frame(
      x_center = numeric(), y_center = numeric(), radius = numeric(), color = character(),
      width = numeric(), alpha = numeric(), arrow_type = character(), arrow_size = numeric(),
      gap_size = numeric(), loop_width = numeric(), loop_height = numeric(), orientation = numeric(),
      lavaan = logical(), two_way = logical(), locked = logical(), group = character(), stringsAsFactors = FALSE
    ),
    group_storage = list(
      sem = list(),
      network = list(),
      modifications = list(),
      modifications_network = list(),
      group_settings = list()
    ),
    bundles = list(),
    undo_stack = list(), # Stack for undo
    redo_stack = list() # Stack for redo
  )

  workflow_status <- reactiveVal("No workflow captured yet")

  group_value <- reactiveVal("1")

  updateSelectInput(session, "element_type", selected = "SEM Diagram")

  debounced_which_group <- debounce(reactive(input$which_group), 3000)

  network_change_click <- reactiveValues(n_count = 0)
  lavaan_change_click <- reactiveValues(n_count = 0)
  lavaan_bundle_loaded <- reactiveValues(yes = 0)
  network_bundle_loaded <- reactiveValues(yes = 0)

  model_state <- reactiveValues( # SEM
    original = NULL,  # Stores initial model
    current = NULL,   # Stores modified model
    code = NULL,      # Stores model syntax
    data = NULL,       # Stores data frame or matrix
    edges = NULL,
    nodes = NULL
  )

  network_state <- reactiveValues(
    nodes = NULL,
    edges = NULL,
    weights = NULL,
    data = NULL
  )

  is_there_data <- reactiveValues(file = FALSE)
  is_there_data_no_bundle <- reactiveValues(file = FALSE)

  rds_path <- getOption("ggsem.path", default = NULL)

  observe({
    if (!is.null(getOption("ggsem.load_metadata")) && getOption("ggsem.load_metadata")) {
      metadata_path <- getOption("ggsem.metadata")

      if (!is.null(metadata_path) && file.exists(metadata_path)) {
        tryCatch({
          workflow <- readRDS(metadata_path)

          if (is_valid_workflow(workflow)) {
            values$points <- workflow$visual_elements$points
            values$lines <- workflow$visual_elements$lines
            values$loops <- workflow$visual_elements$loops
            values$annotations <- workflow$visual_elements$annotations

            values$group_storage$sem <- workflow$sem_groups
            values$group_storage$network <- workflow$network_groups
            values$group_storage$modifications <- workflow$modifications$sem
            values$group_storage$modifications_network <- workflow$modifications$network

            if (!is.null(workflow$exported_history)) {
              if (!is.null(workflow$exported_history$undo_stack)) {
                values$undo_stack <- workflow$exported_history$undo_stack %||% list()
                values$redo_stack <- workflow$exported_history$redo_stack %||% list()

                history_info <- tolower(workflow$exported_history$history_summary) %||% "unknown"
                showNotification(
                  paste0("Loaded workflow with ", history_info,
                        " (", length(values$undo_stack), " undo steps)"),
                  type = "message",
                  duration = 5
                )
              } else {
                values$undo_stack <- list()
                values$redo_stack <- list()
                showNotification(
                  "Workflow loaded (no undo history saved)",
                  type = "message",
                  duration = 3
                )
              }
            }
            # Backward compatibility with OLD compressed format
            else if (!is.null(workflow$history_state)) {
              # Old format might be compressed - check and decompress if needed
              if (!is.null(workflow$history_state$undo_stack)) {
                # Check if compressed (old format)
                if (length(workflow$history_state$undo_stack) > 0 &&
                    is.raw(workflow$history_state$undo_stack[[1]])) {
                  # Compressed - decompress
                  decompress_stack <- function(stack) {
                    lapply(stack, function(compressed_state) {
                      decompressed <- memDecompress(compressed_state, type = "gzip")
                      unserialize(decompressed)
                    })
                  }
                  values$undo_stack <- decompress_stack(workflow$history_state$undo_stack %||% list())
                  values$redo_stack <- decompress_stack(workflow$history_state$redo_stack %||% list())
                } else {
                  # Already uncompressed (very old format)
                  values$undo_stack <- workflow$history_state$undo_stack %||% list()
                  values$redo_stack <- workflow$history_state$redo_stack %||% list()
                }

                history_info <- tolower(workflow$metadata$history_summary) %||% "complete history"
                showNotification(
                  paste("Loaded workflow with", history_info,
                        " (", length(values$undo_stack), "undo steps)"),
                  type = "message",
                  duration = 5
                )
              } else {
                values$undo_stack <- list()
                values$redo_stack <- list()
                showNotification(
                  "Workflow loaded (no undo history saved)",
                  type = "message",
                  duration = 3
                )
              }
            }
            else {
              # No history at all
              values$undo_stack <- list()
              values$redo_stack <- list()
              showNotification(
                "Workflow loaded (no undo history)",
                type = "message",
                duration = 3
              )
            }

            shinyjs::delay(500, {
              loaded_groups <- sort(unique(c(
                values$points$group,
                values$lines$group,
                values$annotations$group,
                values$loops$group
              )))
              loaded_groups <- loaded_groups[!is.na(loaded_groups) & loaded_groups != ""]

              if (length(loaded_groups) > 0) {
                first_grp <- loaded_groups[1]

                # Sync the reactive values first
                last_used_group(first_grp)
                last_modify_group(first_grp)
                last_position_group(first_grp)
                last_delete_group(first_grp)
                last_lock_group(first_grp)

                ids <- c("group_select", "modify_group_select", "position_group_select",
                         "delete_group_select", "lock_group_select")

                for(id in ids) {
                  updateSelectInput(session, id,
                                    choices = loaded_groups,
                                    selected = first_grp)
                }
              }
            })

            output$plot <- renderPlot({
              recreate_plot()
            })

            options(ggsem.load_metadata = NULL)
            options(ggsem.metadata = NULL)
          } else {
            showNotification("Invalid workflow file format", type = "error")
          }
        }, error = function(e) {
          showNotification(paste("Error loading metadata:", e$message), type = "error")
        })
      }
    }
  })

  if (!is.null(rds_path) && file.exists(rds_path)) {
    bundles <- readRDS(rds_path)

    if (!is.null(bundles$object) || !is.null(bundles$session)) {
      # This is a single bundle (old format)
      bundle_list <- list(default = bundles)
      group_names <- bundles$group
      names(bundle_list) <- group_names
    } else {
      # This is a list of multiple bundles
      bundle_list <- bundles
      group_names <- names(bundle_list)
    }

    values$bundles <- bundle_list

    combined_points <- data.frame()
    combined_lines <- data.frame()
    combined_annotations <- data.frame()

    observe({
      req(values$bundles, lavaan_change_click$n_count == 0, network_change_click$n_count == 0,
          lavaan_bundle_loaded$yes == 0, network_bundle_loaded$yes == 0)

      for (group_id in group_names) {

        bundle <- values$bundles[[group_id]]

        if (!is.null(bundle$graph_data$points) && nrow(bundle$graph_data$points) > 0) {
          combined_points <- bind_rows(combined_points, bundle$graph_data$points)
        }
        if (!is.null(bundle$graph_data$lines) && nrow(bundle$graph_data$lines) > 0) {
          combined_lines <- bind_rows(combined_lines, bundle$graph_data$lines)
        }
        if (!is.null(bundle$graph_data$annotations) && nrow(bundle$graph_data$annotations) > 0) {
          combined_annotations <- bind_rows(combined_annotations, bundle$graph_data$annotations)
        }

        observe({
          if (nrow(combined_points) > 0) {
            values$points <- combined_points
          }
        })

        observe({
          if (nrow(combined_lines) > 0) {
            values$lines <- combined_lines
          }
        })

        observe({
          if (nrow(combined_annotations) > 0) {
            values$annotations <- combined_annotations
          }
        })

        which_type = bundle$graph_data$which_type


        if (!is.null(bundle$object)) {
          if (which_type == 'sem') {

            if (is.null(values$group_storage$sem[[group_id]])) {
              if (is.null(values$group_storage$sem)) {
                values$group_storage$sem <- list()
              }
              values$group_storage$sem[[group_id]] <- create_group_storage(type = "sem")
            }

            if (inherits(bundle$object, c("lavaan"))) {

              # Extract data with group variable included
              lavaan_data <- lavInspect(bundle$object, "data")
              if (inherits(lavaan_data, "matrix")) {
                lavaan_data <- as.data.frame(lavaan_data)
              } else if (is.list(lavaan_data)) {
                # Get the group variable name from the lavaan object
                group_var_name <- lavInspect(bundle$object, "group")
                if (is.null(group_var_name)) {
                  group_var_name <- "Group"  # default name if not found
                }

                # Add group variable to each dataset and then rbind
                group_names <- names(lavaan_data)
                lavaan_data <- do.call(rbind, lapply(seq_along(lavaan_data), function(i) {
                  group_data <- as.data.frame(lavaan_data[[i]])
                  group_data[[group_var_name]] <- group_names[i]
                  group_data
                }))
              }

              values$group_storage$sem[[group_id]]$data <- lavaan_data
              values$group_storage$sem[[group_id]]$last_lavaan_layout <- "custom"
              values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix <- bundle$graph_data$layout
              values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix0 <- bundle$graph_data$layout
              values$group_storage$sem[[group_id]]$last_std_est <- FALSE
              values$group_storage$sem[[group_id]]$last_ustd_est <- TRUE
              values$group_storage$sem[[group_id]]$last_conf_int <- FALSE
              values$group_storage$sem[[group_id]]$last_p_val <- FALSE
              values$group_storage$sem[[group_id]]$last_group_level <- bundle$group_level
              values$group_storage$sem[[group_id]]$last_p_val_alpha <- 0.05

              if (is(bundle$object)[[1]] == "lavaan") obj_type <- 'lavaan'
              if (is(bundle$object)[[1]] == "blavaan") obj_type <- 'blavaan'

              group_labels <- lavInspect(bundle$object, "group.label")
              if (length(group_labels) == 0) {
                multigroup_data_upload <- FALSE
              } else {
                multigroup_data_upload <- TRUE
              }

              group_var <- NULL
              # group_level <- group_id

              if (obj_type == 'lavaan') {
                values$group_storage$sem[[group_id]]$last_lavaan_syntax <- bundle$lavaan_string #fit_to_lavstring(bundle$object)
                sem_paths <- bundle$graph_data$sem_paths

              } else if (obj_type == 'blavaan') {
                values$group_storage$sem[[group_id]]$last_lavaan_syntax <- bundle$lavaan_string#blavaan_to_lavstring(bundle$object)
                sem_paths <- bundle$graph_data$sem_paths
              }


              values$group_storage$sem[[group_id]]$last_sem_paths <- sem_paths
              node_names <- names(sem_paths$graphAttributes$Nodes$labels)
              if (is.null(node_names)) node_names <- sem_paths$graphAttributes$Nodes$labels

              # Process edges
              edges_df0 <- data.frame(
                from = sem_paths$Edgelist$from,
                to = sem_paths$Edgelist$to,
                weight = sem_paths$Edgelist$weight,
                directed = sem_paths$Edgelist$directed,
                bidirectional = sem_paths$Edgelist$bidirectional,
                labels = sem_paths$graphAttributes$Edges$labels,
                sig = ifelse(sem_paths$graphAttributes$Edge$color == "#000000FF", TRUE, FALSE)
              )

              edges_df <- edges_df0[!duplicated(
                t(apply(edges_df0[c("from", "to")], 1, sort))
              ), ]

              edges_df <- edges_df[edges_df$from != edges_df$to, ]
              edge_op <- ifelse(edges_df$bidirectional, "~~", "~")

              # Handle intercepts
              intercept_indices <- which(node_names == "1")
              intercept_sources <- character(length(intercept_indices))

              for (i in seq_along(intercept_indices)) {
                intercept_idx <- intercept_indices[i]

                connected_edges <- edges_df[edges_df$from == intercept_idx | edges_df$to == intercept_idx, ]

                if (nrow(connected_edges) > 0) {
                  target_nodes <- c(connected_edges$from, connected_edges$to)
                  target_nodes <- target_nodes[target_nodes != intercept_idx]

                  if (length(target_nodes) > 0) {
                    target_var <- node_names[target_nodes[1]]
                    intercept_sources[i] <- paste0("Intercept_", target_var)
                  } else {
                    intercept_sources[i] <- paste0("Intercept_", i)
                  }
                } else {
                  intercept_sources[i] <- paste0("Intercept_", i)
                }
              }

              node_names[intercept_indices] <- intercept_sources

              edges_from <- node_names[edges_df$from]
              edges_to <- node_names[edges_df$to]


            } else if (inherits(bundle$object, c("MxRAMModel", "mplusObject"))) {
              model_state$current <- bundle$model_obj

              if (inherits(bundle$object, c("mplusObject"))) obj_type <- 'MPlus'
              if (inherits(bundle$object, c("MxRAMModel"))) obj_type <- 'OpenMX'

              sem_paths <-  bundle$graph_data$sem_paths
              # values$group_storage$sem[[group_id]]$last_lavaan_syntax <- bundle$lavaan_string

              node_names <- names(sem_paths$graphAttributes$Nodes$labels)
              if (is.null(node_names)) node_names <- sem_paths$graphAttributes$Nodes$labels

              # Process edges
              edges_df0 <- data.frame(
                from = sem_paths$Edgelist$from,
                to = sem_paths$Edgelist$to,
                weight = sem_paths$Edgelist$weight,
                directed = sem_paths$Edgelist$directed,
                bidirectional = sem_paths$Edgelist$bidirectional,
                labels = sem_paths$graphAttributes$Edges$labels,
                sig = ifelse(sem_paths$graphAttributes$Edge$color == "#000000FF", TRUE, FALSE)
              )

              edges_df <- edges_df0[!duplicated(
                t(apply(edges_df0[c("from", "to")], 1, sort))
              ), ]

              edges_df <- edges_df[edges_df$from != edges_df$to, ]
              edge_op <- ifelse(edges_df$bidirectional, "~~", "~")

              # Handle intercepts
              intercept_indices <- which(node_names == "1")
              intercept_sources <- character(length(intercept_indices))

              for (i in seq_along(intercept_indices)) {
                intercept_idx <- intercept_indices[i]

                connected_edges <- edges_df[edges_df$from == intercept_idx | edges_df$to == intercept_idx, ]

                if (nrow(connected_edges) > 0) {
                  target_nodes <- c(connected_edges$from, connected_edges$to)
                  target_nodes <- target_nodes[target_nodes != intercept_idx]

                  if (length(target_nodes) > 0) {
                    target_var <- node_names[target_nodes[1]]
                    intercept_sources[i] <- paste0("Intercept_", target_var)
                  } else {
                    intercept_sources[i] <- paste0("Intercept_", i)
                  }
                } else {
                  intercept_sources[i] <- paste0("Intercept_", i)
                }
              }

              node_names[intercept_indices] <- intercept_sources
              edges_from <- node_names[edges_df$from]
              edges_to <- node_names[edges_df$to]


              if (inherits(bundle$object, c("MxRAMModel"))) {
                values$group_storage$sem[[group_id]]$data <- bundle$object$data$observed
                values$group_storage$sem[[group_id]]$last_lavaan_layout <- "custom"
                values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix <- bundle$graph_data$layout
                values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix0 <- bundle$graph_data$layout
                values$group_storage$sem[[group_id]]$last_sem_paths <- sem_paths
              } else if (inherits(bundle$object, c("mplusObject"))) {
                values$group_storage$sem[[group_id]]$data <- if (!is.null(bundle$object$rdata)) bundle$object$rdata else bundle$object$data
                values$group_storage$sem[[group_id]]$last_lavaan_layout <- "custom"
                values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix <- bundle$graph_data$layout
                values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix0 <- bundle$graph_data$layout
                values$group_storage$sem[[group_id]]$last_sem_paths <- sem_paths
              }

              values$group_storage$sem[[group_id]]$last_group_level <- bundle$group_level
              values$group_storage$sem[[group_id]]$last_lavaan_syntax <- bundle$lavaan_string

            } else if (inherits(bundle$object, c("sem_graph"))) {

              node_names <- bundle$object$nodes$name
              node_types <- bundle$object$nodes$shape


              edges_df0 <- data.frame(
                from = bundle$object$edges$from,
                to = bundle$object$edges$to,
                directed = bundle$object$edges$arrow == "last", # directed
                bidirectional = bundle$object$edges$arrow == "none", # covariance
                labels = bundle$object$edges$label
              )
              edges_df <- edges_df0[!duplicated(
                t(apply(edges_df0[c("from", "to")], 1, sort))
              ), ]

              edges_df <- edges_df[edges_df$from != edges_df$to, ]
              edges_from <- edges_df$from
              edges_to <- edges_df$to
              edge_labels <- edges_df$labels


              values$group_storage$sem[[group_id]]$last_lavaan_syntax <- bundle$lavaan_string
              values$group_storage$sem[[group_id]]$last_group_level <- bundle$group_level

              if (is(bundle$model_obj)[[1]] == "lavaan") {
                obj_type <- 'tidySEM + lavaan'
                lavaan_data <- lavInspect(bundle$model_obj, "data")
                if (inherits(lavaan_data, "matrix")) {
                  lavaan_data <- as.data.frame(lavaan_data)
                } else if (is.list(lavaan_data)) {
                  # Get the group variable name from the lavaan object
                  group_var_name <- lavInspect(bundle$model_obj, "group")
                  if (is.null(group_var_name)) {
                    group_var_name <- "Group"  # default name if not found
                  }

                  # Add group variable to each dataset and then rbind
                  group_names <- names(lavaan_data)
                  lavaan_data <- do.call(rbind, lapply(seq_along(lavaan_data), function(i) {
                    group_data <- as.data.frame(lavaan_data[[i]])
                    group_data[[group_var_name]] <- group_names[i]
                    group_data
                  }))
                }
                values$group_storage$sem[[group_id]]$data <- lavaan_data
                values$group_storage$sem[[group_id]]$last_p_val <- TRUE

              } else if (is(bundle$model_obj)[[1]] == "blavaan") {
                obj_type <- 'tidySEM + blavaan'
                values$group_storage$sem[[group_id]]$last_p_val <- FALSE
                lavaan_data <- blavInspect(bundle$model_obj, "data")
                if (inherits(lavaan_data, "matrix")) {
                  lavaan_data <- as.data.frame(lavaan_data)
                } else if (is.list(lavaan_data)) {
                  # Get the group variable name from the blavaan object
                  group_var_name <- blavInspect(bundle$model_obj, "group")
                  if (is.null(group_var_name)) {
                    group_var_name <- "Group"  # default name if not found
                  }

                  # Add group variable to each dataset and then rbind
                  group_names <- names(lavaan_data)
                  lavaan_data <- do.call(rbind, lapply(seq_along(lavaan_data), function(i) {
                    group_data <- as.data.frame(lavaan_data[[i]])
                    group_data[[group_var_name]] <- group_names[i]
                    group_data
                  }))
                }
                values$group_storage$sem[[group_id]]$data <- lavaan_data
              } else if (is(bundle$model_obj)[[1]] == "MxRAMModel") {
                obj_type <- 'tidySEM + openMx'
                values$group_storage$sem[[group_id]]$data <- bundle$model_obj$data$observed
                values$group_storage$sem[[group_id]]$last_p_val <- FALSE
              } else if (is(bundle$model_obj)[[1]] == "mplusObject") {
                obj_type <- 'tidySEM + Mplus'
                values$group_storage$sem[[group_id]]$data <- if (!is.null(bundle$model_obj$rdata)) bundle$model_obj$rdata else bundle$model_obj$data
                values$group_storage$sem[[group_id]]$last_p_val <- FALSE
              }

              values$group_storage$sem[[group_id]]$last_p_val <- TRUE
              values$group_storage$sem[[group_id]]$last_std_est <- FALSE
              values$group_storage$sem[[group_id]]$last_ustd_est <- TRUE
              values$group_storage$sem[[group_id]]$last_conf_int <- FALSE

            } else if (inherits(bundle$object, c("qgraph"))) {
              if (inherits(bundle$model_obj, c("lavaan"))) {

                if (is(bundle$model_obj)[[1]] == "lavaan") {
                  values$group_storage$sem[[group_id]]$last_lavaan_syntax <- bundle$lavaan_string
                  values$group_storage$sem[[group_id]]$last_sem_paths <- bundle$object
                  values$group_storage$sem[[group_id]]$last_std_est <- FALSE
                  values$group_storage$sem[[group_id]]$last_ustd_est <- TRUE
                  values$group_storage$sem[[group_id]]$last_conf_int <- FALSE
                  values$group_storage$sem[[group_id]]$last_p_val <- FALSE
                  values$group_storage$sem[[group_id]]$last_group_level <- bundle$group_level
                  values$group_storage$sem[[group_id]]$last_p_val_alpha <- 0.05

                  # Extract data with group variable included
                  lavaan_data <- lavInspect(bundle$model_obj, "data")
                  if (inherits(lavaan_data, "matrix")) {
                    lavaan_data <- as.data.frame(lavaan_data)
                  } else if (is.list(lavaan_data)) {
                    group_var_name <- lavInspect(bundle$model_obj, "group")
                    if (is.null(group_var_name)) {
                      group_var_name <- "Group"  # default name if not found
                    }
                    group_names <- names(lavaan_data)
                    lavaan_data <- do.call(rbind, lapply(seq_along(lavaan_data), function(i) {
                      group_data <- as.data.frame(lavaan_data[[i]])
                      group_data[[group_var_name]] <- group_names[i]
                      group_data
                    }))
                  }

                  values$group_storage$sem[[group_id]]$data <- lavaan_data
                  values$group_storage$sem[[group_id]]$last_lavaan_layout <- "custom"
                  values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix <- bundle$graph_data$layout
                  values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix0 <- bundle$graph_data$layout
                  obj_type <- 'semPaths + lavaan'

                } else if (is(bundle$model_obj)[[1]] == "blavaan") {
                  values$group_storage$sem[[group_id]]$last_lavaan_syntax <- bundle$lavaan_string
                  values$group_storage$sem[[group_id]]$last_sem_paths <- bundle$object
                  values$group_storage$sem[[group_id]]$last_std_est <- FALSE
                  values$group_storage$sem[[group_id]]$last_ustd_est <- TRUE
                  values$group_storage$sem[[group_id]]$last_conf_int <- FALSE
                  values$group_storage$sem[[group_id]]$last_p_val <- FALSE
                  values$group_storage$sem[[group_id]]$last_group_level <- bundle$group_level

                  # Extract data with group variable included for blavaan object
                  lavaan_data <- blavInspect(bundle$model_obj, "data")
                  if (inherits(lavaan_data, "matrix")) {
                    lavaan_data <- as.data.frame(lavaan_data)
                  } else if (is.list(lavaan_data)) {
                    # Get the group variable name from the blavaan object
                    group_var_name <- blavInspect(bundle$model_obj, "group")
                    if (is.null(group_var_name)) {
                      group_var_name <- "Group"  # default name if not found
                    }

                    # Add group variable to each dataset and then rbind
                    group_names <- names(lavaan_data)
                    lavaan_data <- do.call(rbind, lapply(seq_along(lavaan_data), function(i) {
                      group_data <- as.data.frame(lavaan_data[[i]])
                      group_data[[group_var_name]] <- group_names[i]
                      group_data
                    }))
                  }

                  values$group_storage$sem[[group_id]]$data <- lavaan_data
                  values$group_storage$sem[[group_id]]$last_lavaan_layout <- "custom"
                  values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix <- bundle$graph_data$layout
                  values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix0 <- bundle$graph_data$layout
                  obj_type <- 'semPaths + blavaan'
                }


                node_names <- names(bundle$object$graphAttributes$Nodes$labels)
                if (is.null(node_names)) node_names <- bundle$object$graphAttributes$Nodes$labels
                # Process edges
                edges_df0 <- data.frame(
                  from = bundle$object$Edgelist$from,
                  to = bundle$object$Edgelist$to,
                  weight = bundle$object$Edgelist$weight,
                  directed = bundle$object$Edgelist$directed,
                  bidirectional = bundle$object$Edgelist$bidirectional,
                  labels = bundle$object$graphAttributes$Edges$labels,
                  sig = ifelse(bundle$object$graphAttributes$Edge$color == "#000000FF", TRUE, FALSE)
                )
                edges_df <- edges_df0[!duplicated(
                  t(apply(edges_df0[c("from", "to")], 1, sort))
                ), ]

                edges_df <- edges_df[edges_df$from != edges_df$to, ]

                edge_op <- ifelse(edges_df$bidirectional, "~~", "~")

                # Handle intercepts
                intercept_indices <- which(node_names == "1")
                intercept_sources <- character(length(intercept_indices))

                for (i in seq_along(intercept_indices)) {
                  intercept_idx <- intercept_indices[i]

                  connected_edges <- edges_df[edges_df$from == intercept_idx | edges_df$to == intercept_idx, ]

                  if (nrow(connected_edges) > 0) {
                    target_nodes <- c(connected_edges$from, connected_edges$to)
                    target_nodes <- target_nodes[target_nodes != intercept_idx]

                    if (length(target_nodes) > 0) {
                      target_var <- node_names[target_nodes[1]]
                      intercept_sources[i] <- paste0("Intercept_", target_var)
                    } else {
                      intercept_sources[i] <- paste0("Intercept_", i)
                    }
                  } else {
                    intercept_sources[i] <- paste0("Intercept_", i)
                  }
                }

                node_names[intercept_indices] <- intercept_sources

                edges_from <- node_names[edges_df$from]
                edges_to <- node_names[edges_df$to]


              } else if (is.null(bundle$model_obj)) {

                node_names <- names(bundle$object$graphAttributes$Nodes$labels)
                if (is.null(node_names)) node_names <- bundle$object$graphAttributes$Nodes$labels

                # Process edges
                edges_df0 <- data.frame(
                  from = bundle$object$Edgelist$from,
                  to = bundle$object$Edgelist$to,
                  weight = bundle$object$Edgelist$weight,
                  directed = bundle$object$Edgelist$directed,
                  bidirectional = bundle$object$Edgelist$bidirectional,
                  labels = bundle$object$graphAttributes$Edges$labels,
                  sig = ifelse(bundle$object$graphAttributes$Edge$color == "#000000FF", TRUE, FALSE)
                )
                edges_df <- edges_df0[!duplicated(
                  t(apply(edges_df0[c("from", "to")], 1, sort))
                ), ]

                edges_df <- edges_df[edges_df$from != edges_df$to, ]
                edge_op <- ifelse(edges_df$bidirectional, "~~", "~")

                # Handle intercepts
                intercept_indices <- which(node_names == "1")
                intercept_sources <- character(length(intercept_indices))

                for (i in seq_along(intercept_indices)) {
                  intercept_idx <- intercept_indices[i]

                  connected_edges <- edges_df[edges_df$from == intercept_idx | edges_df$to == intercept_idx, ]

                  if (nrow(connected_edges) > 0) {
                    target_nodes <- c(connected_edges$from, connected_edges$to)
                    target_nodes <- target_nodes[target_nodes != intercept_idx]

                    if (length(target_nodes) > 0) {
                      target_var <- node_names[target_nodes[1]]
                      intercept_sources[i] <- paste0("Intercept_", target_var)
                    } else {
                      intercept_sources[i] <- paste0("Intercept_", i)
                    }
                  } else {
                    intercept_sources[i] <- paste0("Intercept_", i)
                  }
                }

                node_names[intercept_indices] <- intercept_sources

                edges_from <- node_names[edges_df$from]
                edges_to <- node_names[edges_df$to]


                obj_type <- 'semPaths'
              }

            } else if (inherits(bundle$object, c('grViz'))) {
              obj_type <- 'grViz'
              if (inherits(bundle$model_obj, c("lavaan"))) {

                values$group_storage$sem[[group_id]]$last_lavaan_syntax <- fit_to_lavstring(bundle$model_obj)

                # Extract data with group variable included
                lavaan_data <- lavInspect(bundle$model_obj, "data")
                if (inherits(lavaan_data, "matrix")) {
                  lavaan_data <- as.data.frame(lavaan_data)
                } else if (is.list(lavaan_data)) {
                  # Get the group variable name from the lavaan object
                  group_var_name <- lavInspect(bundle$model_obj, "group")
                  if (is.null(group_var_name)) {
                    group_var_name <- "Group"  # default name if not found
                  }

                  # Add group variable to each dataset and then rbind
                  group_names <- names(lavaan_data)
                  lavaan_data <- do.call(rbind, lapply(seq_along(lavaan_data), function(i) {
                    group_data <- as.data.frame(lavaan_data[[i]])
                    group_data[[group_var_name]] <- group_names[i]
                    group_data
                  }))
                }

                values$group_storage$sem[[group_id]]$data <- lavaan_data


                obj_type <- 'lavaanPlot'

                dot_code <- bundle$object$x$diagram
                node_matches <- str_match_all(dot_code, "(\\w+)\\s*\\[([^\\]]+)\\]")[[1]]

                node_df <- data.frame(
                  id = node_matches[, 2],
                  attrs = node_matches[, 3],
                  stringsAsFactors = FALSE
                ) |>
                  filter(nchar(attrs) > 0)  |>
                  mutate(
                    attrs = map(attrs, ~str_split(.x, ",\\s*")[[1]] |>
                                  discard(~.x == ""))  # Remove empty attributes
                  ) |>
                  unnest(attrs) |>
                  mutate(
                    # Handle cases where '=' might be missing
                    attr_val = ifelse(grepl("=", attrs), attrs, paste0(attrs, "=TRUE"))
                  ) |>
                  separate(attr_val, into = c("attr", "value"), sep = "=", extra = "merge")

                edge_df <- dot_code |>
                  str_extract_all("(\\w+)\\s*->\\s*(\\w+)", simplify = TRUE) |>
                  as.data.frame() |>
                  setNames(c("full", "from", "to"))

                svg_data <- export_svg(bundle$object)
                svg <- read_xml(svg_data)
                xml_ns_strip(svg)
                nodes <- xml_find_all(svg, "//g[contains(@class, 'node')]")
                edges <- xml_find_all(svg, "//g[contains(@class, 'edge')]")

                nodes_df <- bind_rows(lapply(nodes, extract_node_properties, svg))

                edges_df0 <- bind_rows(lapply(edges, extract_edge_properties, svg))
                edges_df <- edges_df0 |>
                  mutate(across(c(label_x, label_y), ~abs(.x)))

                name_to_label <- setNames(nodes_df$label, nodes_df$node_name)

                edges_df$source <- name_to_label[edges_df$source]
                edges_df$target <- name_to_label[edges_df$target]

                edges_from <- edges_df$source
                edges_to <- edges_df$target

                node_names <- nodes_df$label

                latent_idx <- which(nodes_df$shape == 'oval')[[1]]
                observed_idx <- which(nodes_df$shape == 'rectangle')[[1]]

                values$group_storage$sem[[group_id]]$last_width_height_ratio_latent <- round(nodes_df$width[latent_idx] / nodes_df$height[latent_idx], 5)
                values$group_storage$sem[[group_id]]$last_width_height_ratio_observed <- round(nodes_df$width[observed_idx] / nodes_df$height[observed_idx], 5)
                values$group_storage$sem[[group_id]]$last_latent_shape <- nodes_df$shape[latent_idx]
                values$group_storage$sem[[group_id]]$last_observed_shape <- nodes_df$shape[observed_idx]
                values$group_storage$sem[[group_id]]$last_node_border_color <- nodes_df$stroke_color[latent_idx]
                values$group_storage$sem[[group_id]]$last_point_color_latent <- nodes_df$fill[latent_idx]
                values$group_storage$sem[[group_id]]$last_point_color_observed <- nodes_df$fill[observed_idx]
                values$group_storage$sem[[group_id]]$last_text_size_latent <- nodes_df$font_size[latent_idx]
                values$group_storage$sem[[group_id]]$last_text_size_others <- nodes_df$font_size[observed_idx]
                values$group_storage$sem[[group_id]]$last_text_fontface_latent <- 'plain'
                values$group_storage$sem[[group_id]]$last_text_fontface_others <- 'plain'
                values$group_storage$sem[[group_id]]$last_text_color_latent <- head(nodes_df$text_color,1)
                values$group_storage$sem[[group_id]]$last_text_color_others <- head(nodes_df$text_color,1)
                values$group_storage$sem[[group_id]]$last_edge_color <- head(edges_df$stroke_color,1)
                values$group_storage$sem[[group_id]]$last_line_width <- head(edges_df$stroke_width,1)
                values$group_storage$sem[[group_id]]$last_line_alpha <- head(edges_df$alpha,1)
                values$group_storage$sem[[group_id]]$last_text_size_edges <- head(edges_df$label_size,1)
                values$group_storage$sem[[group_id]]$last_text_color_edges <- head(edges_df$label_color,1)
                values$group_storage$sem[[group_id]]$last_text_alpha_edges <- head(edges_df$label_alpha,1)
              }
            }
            if (!is.null(group_id) || group_id != "") {
              if (inherits(bundle$object, c("lavaan"))) {

                values$group_storage$sem[[group_id]]$bundleObject <- bundle$object
                values$group_storage$sem[[group_id]]$bundleModelObject <- bundle$object
                values$group_storage$sem[[group_id]]$original <- bundle$object
                values$group_storage$sem[[group_id]]$current <- bundle$object

                values$group_storage$sem[[group_id]]$edges <- data.frame(source = edges_from, target = edges_to)
                values$group_storage$sem[[group_id]]$nodes <- data.frame(node = node_names)
                values$group_storage$sem[[group_id]]$data_file <- TRUE

                values$group_storage$sem[[group_id]]$last_center_x_position <- bundle$center_x
                values$group_storage$sem[[group_id]]$last_center_y_position <- bundle$center_y

                values$group_storage$sem[[group_id]]$last_relative_x_position <- bundle$width
                values$group_storage$sem[[group_id]]$last_relative_y_position <- bundle$height
                values$group_storage$sem[[group_id]]$multi_group_sem_combine_menu <- FALSE
                values$group_storage$sem[[group_id]]$last_residuals <- FALSE

              } else {

                values$group_storage$sem[[group_id]]$bundleObject <- bundle$object
                values$group_storage$sem[[group_id]]$bundleModelObject <- bundle$model_obj
                values$group_storage$sem[[group_id]]$original <- bundle$model_obj
                values$group_storage$sem[[group_id]]$current <- bundle$model_obj

                values$group_storage$sem[[group_id]]$edges <- data.frame(source = edges_from, target = edges_to)
                values$group_storage$sem[[group_id]]$nodes <- data.frame(node = node_names)
                values$group_storage$sem[[group_id]]$data_file <- TRUE

                values$group_storage$sem[[group_id]]$last_center_x_position <- bundle$center_x
                values$group_storage$sem[[group_id]]$last_center_y_position <- bundle$center_y

                values$group_storage$sem[[group_id]]$last_relative_x_position <- bundle$width
                values$group_storage$sem[[group_id]]$last_relative_y_position <- bundle$height
                values$group_storage$sem[[group_id]]$multi_group_sem_combine_menu <- FALSE
                values$group_storage$sem[[group_id]]$last_residuals <- FALSE

              }
            }

          } else if (which_type == "network") {

            if (is.null(values$group_storage$network[[group_id]])) {
              if (is.null(values$group_storage$network)) {
                values$group_storage$network <- list()
              }
              values$group_storage$network[[group_id]] <- create_group_storage(type = "network")
            }

            if (inherits(bundle$object, c("qgraph"))) {
              obj_type <- 'qgraph'

              node_names <- names(bundle$object$graphAttributes$Nodes$labels)
              if (is.null(node_names)) node_names <- as.character(bundle$object$graphAttributes$Nodes$labels)

              # Process edges
              edges_df0 <- data.frame(
                from = bundle$object$Edgelist$from,
                to = bundle$object$Edgelist$to
              )
              edges_df <- edges_df0[!duplicated(
                t(apply(edges_df0[c("from", "to")], 1, sort))
              ), ]

              edges_from <- node_names[edges_df$from]
              edges_to <- node_names[edges_df$to]

              values$group_storage$network[[group_id]]$edges <- data.frame(source = edges_from, target = edges_to)
              values$group_storage$network[[group_id]]$nodes <- data.frame(node = node_names)
              values$group_storage$network[[group_id]]$bundleObject <- bundle$object

              values$group_storage$network[[group_id]]$last_x_center <- bundle$center_x
              values$group_storage$network[[group_id]]$last_y_center <- bundle$center_y

              values$group_storage$network[[group_id]]$last_layout_x <- bundle$width
              values$group_storage$network[[group_id]]$last_layout_y <- bundle$height

              values$group_storage$network[[group_id]]$last_random_seed <- bundle$random_seed
              values$group_storage$network[[group_id]]$last_line_endpoint_spacing <- 0


            } else if (inherits(bundle$object, c("network"))) {
              obj_type <- 'network'
              nodes <- data.frame(node = network::network.vertex.names(bundle$object))
              if ("weights" %in% network::list.edge.attributes(bundle$object)) {
                edges <- data.frame(source = network::as.edgelist(bundle$object)[,1],
                                    target = network::as.edgelist(bundle$object)[,2],
                                    weight = network::get.edge.attribute(bundle$object, 'weights'))
              } else {
                edges <- data.frame(source = network::as.edgelist(bundle$object)[,1],
                                    target = network::as.edgelist(bundle$object)[,2],
                                    weight = NA)
              }


              edges$source <- nodes$node[edges$source]
              edges$target <- nodes$node[edges$target]

              # network_state$nodes <- nodes

              edges <- edges |>
                mutate(
                  edge_id = pmin(source, target),
                  edge_pair = pmax(source, target)
                ) |>
                group_by(edge_id, edge_pair) |>
                summarise(
                  source = dplyr::first(source),
                  target = dplyr::first(target),
                  weight = if ("weight" %in% colnames(edges)) {
                    if (is.numeric(weight)) {
                      # Handle numeric weights with NA control
                      if (all(is.na(weight))) NA_real_
                      else mean(weight, na.rm = TRUE)
                    } else {
                      # Handle character/string weights
                      if (all(is.na(weight))) NA_character_
                      else dplyr::first(stats::na.omit(weight)) # Take first non-NA value
                    }
                  }
                  ,
                  two_way = dplyr::n() > 1,
                  .groups = "drop"
                )

              values$group_storage$network[[group_id]]$edges <- edges
              values$group_storage$network[[group_id]]$nodes <- nodes
              values$group_storage$network[[group_id]]$bundleObject <- bundle$object

              values$group_storage$network[[group_id]]$last_x_center <- bundle$center_x
              values$group_storage$network[[group_id]]$last_y_center <- bundle$center_y

              values$group_storage$network[[group_id]]$last_layout_x <- bundle$width
              values$group_storage$network[[group_id]]$last_layout_y <- bundle$height
              values$group_storage$network[[group_id]]$last_is_directed <- network::is.directed(bundle$object)

              values$group_storage$network[[group_id]]$last_random_seed <- bundle$random_seed
              values$group_storage$network[[group_id]]$last_layout_method <- "fr"
              values$group_storage$network[[group_id]]$layout <- bundle$graph_data$layout

            } else if (inherits(bundle$object, c("igraph"))) {
              obj_type <- 'igraph'

              nodes <- igraph::as_data_frame(bundle$object, what = "vertices")

              if (ncol(nodes) == 0) {
                # No vertex attributes at all
                nodes <- data.frame(name = as.character(1:igraph::vcount(bundle$object)))
              } else if (!"name" %in% colnames(nodes)) {
                # Has vertex attributes but no name column
                nodes$name <- as.character(1:igraph::vcount(bundle$object))
              } else {
                nodes$name <- as.character(nodes$name)
              }

              # network_state$nodes <- data.frame(node = nodes$name)

              if (!is.null(E(bundle$object)$weight)) {
                edges <- data.frame(source = as_edgelist(bundle$object)[,1],
                                    target = as_edgelist(bundle$object)[,2],
                                    weight = E(bundle$object)$weight)
              } else {
                edges <- data.frame(source = as_edgelist(bundle$object)[,1],
                                    target = as_edgelist(bundle$object)[,2],
                                    weight = NA)
              }


              edges <- edges |>
                mutate(
                  edge_id = pmin(source, target),
                  edge_pair = pmax(source, target)
                ) |>
                group_by(edge_id, edge_pair) |>
                summarise(
                  source = dplyr::first(source),
                  target = dplyr::first(target),
                  weight = if ("weight" %in% colnames(edges)) {
                    if (is.numeric(weight)) {
                      # Handle numeric weights with NA control
                      if (all(is.na(weight))) NA_real_
                      else mean(weight, na.rm = TRUE)
                    } else {
                      # Handle character/string weights
                      if (all(is.na(weight))) NA_character_
                      else dplyr::first(stats::na.omit(weight)) # Take first non-NA value
                    }
                  }
                  ,
                  two_way = dplyr::n() > 1,
                  .groups = "drop"
                )


              values$group_storage$network[[group_id]]$edges <- edges
              values$group_storage$network[[group_id]]$nodes <- data.frame(node = nodes$name)
              values$group_storage$network[[group_id]]$bundleObject <- bundle$object
              values$group_storage$network[[group_id]]$weights <- E(bundle$object)$weight

              values$group_storage$network[[group_id]]$last_x_center <- bundle$center_x
              values$group_storage$network[[group_id]]$last_y_center <- bundle$center_y

              values$group_storage$network[[group_id]]$last_layout_x <- bundle$width
              values$group_storage$network[[group_id]]$last_layout_y <- bundle$height
              values$group_storage$network[[group_id]]$last_is_directed <- igraph::is_directed(bundle$object)

              values$group_storage$network[[group_id]]$last_random_seed <- bundle$random_seed
              values$group_storage$network[[group_id]]$last_layout_method <- "fr"
              values$group_storage$network[[group_id]]$layout <- bundle$graph_data$layout
            }
          }
          showNotification(
            HTML(paste("Successfully uploaded", "<b>", obj_type, "</b>", "object for",
                       "<b>", ifelse(which_type == 'sem', 'SEM', 'network'), "</b>",
                       "visualization in group", "<b>", group_id, "</b>")),
            type = "message")
        }
      }

      lavaan_bundle_loaded$yes <- 1
      network_bundle_loaded$yes <- 1

    })

    selected_session <- switch(last(bundle_list)$session,
                               "point" = "Point",
                               "line" = "Line",
                               "annotation" = "Text Annotation",
                               "loop" = "Self-loop Arrow",
                               "sem" = "SEM Diagram",
                               "network" = "Network Diagram")

    updateSelectInput(session, "element_type", selected = selected_session)

    output$plot <- renderPlot({
      recreate_plot()
    })

    unlink(rds_path)
  }

  output$edge_spacing_ui <- renderUI({
    if (input$edge_type == "Line") {
      numericInput("auto_endpoint_spacing", "Edge Spacing:", value = 0, min = 0, step = 0.1)
    } else if (input$edge_type == "Arrow") {
      numericInput("auto_endpoint_spacing", "Edge Spacing:", value = 1, min = 0, step = 0.1)
    }
  })

  output$sig_diff_stats_options <- renderUI({

    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    if (is_blavaan()) {
      # Bayesian options
      tagList(
        fluidRow(
          column(12, selectInput("bayes_significance", "Significance Criterion",
                                 choices = c("Credible Interval Excludes 0" = "excludes_zero",
                                             "Outside Region of Practical Equivalence" = "excludes_rope"),
                                 selected = settings$last_bayes_significance %||%  "excludes_zero"))
        ),
        conditionalPanel(
          condition = "input.bayes_significance == 'excludes_rope'",
          fluidRow(
            column(6, numericInput("min_rope", "ROPE Lower Bound:", value = settings$last_min_rope %||% -0.1, step = 0.01)),
            column(6, numericInput("max_rope", "ROPE Upper Bound:", value = settings$last_max_rope %||% 0.1, step = 0.01))
          )
        )
      )
    } else {
      # Frequentist options
      fluidRow(
        column(6, numericInput("p_val_alpha_compare", "Significance Level (α):",
                               value = settings$last_p_val_alpha_compare %||% 0.05, min = 0.001, max = 0.2, step = 0.01))
      )
    }
  })

  output$ai_model_instructions <- renderUI({
    instructions <- switch(input$ai_model,
                           "gemini" = HTML("<small>Get free API key from <a href='https://aistudio.google.com/' target='_blank'>Google AI Studio</a></small>"),
                           "openai" = HTML("<small>Get API key from <a href='https://platform.openai.com/' target='_blank'>OpenAI Platform</a></small>"),
                           "mistral" = HTML("<small>Get API key from <a href='https://console.mistral.ai/' target='_blank'>Mistral AI Console</a></small>"),
                           "claude" = HTML("<small>Get API key from <a href='https://console.anthropic.com/' target='_blank'>Anthropic Console</a></small>"),
                           "ollama" = HTML("<small>Install Ollama from <a href='https://ollama.ai/' target='_blank'>ollama.ai</a> and pull models first</small>")
    )

    tags$div(instructions, style = "margin-top: 5px; color: #666;")
  })

  output$ai_model_settings <- renderUI({
    tagList(
      switch(input$ai_model,
             "gemini" = passwordInput("gemini_api_key", "Gemini API Key:",
                                      placeholder = "Enter your Gemini API key"),
             "openai" = passwordInput("openai_api_key", "OpenAI API Key:",
                                      placeholder = "Enter your OpenAI API key"),
             "mistral" = passwordInput("mistral_api_key", "Mistral API Key:",
                                       placeholder = "Enter your Mistral API key"),
             "claude" = passwordInput("claude_api_key", "Claude API Key:",
                                      placeholder = "Enter your Claude API key"),
             "ollama" = selectInput("ollama_model", "Ollama Model:",
                                    choices = c("llama2" = "llama2",
                                                "codellama" = "codellama",
                                                "mistral" = "mistral",
                                                "mixtral" = "mixtral"),
                                    selected = "llama2")
      ),
      uiOutput("ai_model_instructions")
    )
  })

  output$lavaan_syntax <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    default_syntax <- "# Enter your lavaan syntax here\n
    visual  =~ x1 + x2 + x3
    textual =~ x4 + x5 + x6
    speed   =~ x7 + x8 + x9
    "

    rds_syntax <- tryCatch({
      as.character(bundles$lavaan_string)
    }, error = function(e) {
      as.character(default_syntax)
    })

    tagList(
      textAreaInput("lavaan_syntax", "Lavaan Syntax",
                    value = settings$last_lavaan_syntax %||% rds_syntax,
                    width = "100%", height = "200px")
    )
  })

  outputOptions(output, "lavaan_syntax", suspendWhenHidden = FALSE)

  output$model_type_selector <- renderUI({
    values$force_ui_refresh
    # if (input$write_sem_code) {
    selectInput(
      "sem_model_type",
      tags$b(style = "font-size: 14px;", "Model Type"),
      choices = c(
        "SEM" = "sem",
        "CFA" = "cfa",
        "EFA" = "efa",
        "Growth" = "growth"
      ),
      selected = "sem"
    )
    # }
  })
  outputOptions(output, "model_type_selector", suspendWhenHidden = FALSE)

  sem_model_type_reactive <- reactive({
    if (is.null(input$sem_model_type)) {
      "sem"
    } else {
      input$sem_model_type
    }
  })

  output$efa_controls <- renderUI({
    values$force_ui_refresh
    if (sem_model_type_reactive() == "efa") {
      tagList(
        numericInput(
          "nfactors",
          tags$b(style = "font-size: 14px;", "Number of Factors"),
          value = 1,
          min = 1,
          max = 20,
          step = 1
        ),
        selectInput(
          "rotation",
          tags$b(style = "font-size: 14px;", "Rotation Method"),
          choices = c(
            "None" = "none",
            "Varimax" = "varimax",
            "Quartimax" = "quartimax",
            "Promax" = "promax",
            "Oblimin" = "oblimin",
            "Geomin" = "geomin"
          ),
          selected = "varimax"
        )
      )
    }
  })
  outputOptions(output, "efa_controls", suspendWhenHidden = FALSE)

  output$sem_code <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    model_type <- if (!is.null(sem_model_type_reactive()) && sem_model_type_reactive() != "") {
      sem_model_type_reactive()
    } else {
      "sem"  # default fallback
    }

    # Safely get multi-group status
    multi_group <- if (!is.null(input$multigroup_data_upload)) {
      input$multigroup_data_upload
    } else {
      FALSE
    }

    # Safely get group variable
    group_var <- if (!is.null(input$group_var) && input$group_var != "") {
      input$group_var
    } else {
      NULL
    }

    default_code <- if (model_type == "efa") {
      nf <- if (!is.null(input$nfactors)) input$nfactors else 1
      rotation <- if (!is.null(input$rotation)) input$rotation else "varimax"
      paste0("efa(lavaan_string, data = data, nfactors = ", nf, ", rotation = \"", rotation, "\")")
    } else if (multi_group && !is.null(group_var)) {
      paste0(model_type, "(lavaan_string, data = data, group = \"", group_var, "\")")
    } else {
      paste0(model_type, "(lavaan_string, data = data)")
    }


    tagList(
      textAreaInput(
        "sem_code",
        label = NULL,
        value = settings$last_sem_code %||% default_code,
        width = "100%",
        height = "120px",
        placeholder = "e.g., cfa(lavaan_string, data = data, estimator = 'MLR')"
      ),
      helpText("Use 'lavaan_string' for the model syntax and 'data' for the dataset.")
    )
  })

  outputOptions(output, "sem_code", suspendWhenHidden = FALSE)

  observeEvent(c(input$multigroup_data_upload, input$group_var, input$sem_model_type, input$nfactors, input$rotation), {
    # if (input$write_sem_code) {

    model_type <- if (!is.null(input$sem_model_type)) input$sem_model_type else "sem"
    multi_group <- if (!is.null(input$multigroup_data_upload)) input$multigroup_data_upload else FALSE
    group_var <- if (!is.null(input$group_var) && input$group_var != "") input$group_var else NULL

    new_code <- if (model_type == "efa") {
      nf <- if (!is.null(input$nfactors)) input$nfactors else 1
      rotation <- if (!is.null(input$rotation)) input$rotation else "varimax"
      paste0("efa(lavaan_string, data = data, nfactors = ", nf, ", rotation = \"", rotation, "\")")
    } else if (multi_group && !is.null(group_var)) {
      paste0(model_type, "(lavaan_string, data = data, group = \"", group_var, "\")")
    } else {
      paste0(model_type, "(lavaan_string, data = data)")
    }

    updateTextAreaInput(session, "sem_code", value = new_code)
    # }
  })


  output$sem_layout <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    layout_choices <- c(
      "Tree" = "tree",
      "Circle" = "circle",
      "Spring" = "spring",
      "Tree2" = "tree2",
      "Circle2" = "circle2",
      "Default" = "default"
    )

    if (!is.null(values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix0)) {
      layout_choices <- c(layout_choices, "Custom" = "custom")
    }

    ai_settings <- list(
      gemini = input$gemini_api_key,
      openai = input$openai_api_key,
      mistral = input$mistral_api_key,
      claude = input$claude_api_key,
      ollama_model = input$ollama_model
    )

    if (any(nzchar(unlist(ai_settings[c("gemini","openai","mistral","claude")])))) {
      layout_choices <- c(layout_choices, "GenAI" = "layout_ai")
    }

    tagList(
      conditionalPanel(
        condition = "output.is_sempath_or_tidysem_or_grViz != true",
        selectInput("lavaan_layout", "Choose Layout Algorithm:",
                    choices = layout_choices,
                    selected = settings$last_lavaan_layout %||% "default"
        )
      ),
      fluidRow(
        column(6, numericInput("center_x_position", "Center X:", value = settings$last_center_x_position %||% 0, step = 1)),
        column(6, numericInput("center_y_position", "Center Y:", value = settings$last_center_y_position %||% 0, step = 1))
      ),
      fluidRow(
        column(
          6,
          numericInput(
            "relative_x_position",
            HTML(paste(icon("ruler-horizontal"), "&nbsp;Width X:")),
            value = settings$last_relative_x_position %||% 25,
            min = 0.1,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "relative_y_position",
            HTML(paste(icon("ruler-vertical"), "&nbsp;Height Y:")),
            value = settings$last_relative_y_position %||% 25,
            min = 0.1,
            step = 0.1
          )
        )
      ),
      checkboxInput(
        "flip_sem_layout",
        tagList(
          icon("retweet", style = "margin-right: 8px; color: #FF6B6B;"),
          tags$b(style = "font-size: 16px;", "Flip SEM Layout"),
        ),
        value = settings$last_flip_sem_layout %||% FALSE
      ),
      conditionalPanel(
        condition = "input.flip_sem_layout",
        selectInput("flip_sem_layout_direction", "Flip Direction",
                    choices = c("Relative to Horizontal" = "horizontal",
                                "Relative to Vertical" = "vertical",
                                "Relative to Horizontal and Vertical" = "both"),
                    selected = settings$last_flip_sem_layout_direction %||%  "horizontal")
      ),
      checkboxInput(
        "rotate_sem_layout",
        tagList(
          icon("sync", style = "margin-right: 8px; color: #3F51B5;"),
          tags$b(style = "font-size: 16px;", "Rotate SEM Layout"),
        ),
        value = settings$last_rotate_sem_layout %||% FALSE
      ),
      conditionalPanel(
        condition = "input.rotate_sem_layout",
        fluidRow(
          column(
            12,
            numericInput(
              "rotate_sem_layout_angle",
              label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Orientation (Degree)")),
              value = settings$last_rotate_sem_layout_angle %||% 0,
              step = 0.1
            )
          )
        )
      ),
      conditionalPanel(
        condition = "input.lavaan_layout == 'layout_ai'",
        fluidRow(
          column(width = 12,
                 textAreaInput(
                   "additional_prompts_layout",
                   "Additional prompts for layout",
                   value = "",
                   placeholder = "Write additional prompts (under 30 words).",
                   width = "100%",
                   height = "100px"
                 )
          )
        )
      )

    )
  })

  outputOptions(output, "sem_layout", suspendWhenHidden = FALSE)

  output$sem_nonselective_node <- renderUI({
    values$force_ui_refresh

    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(

      fluidRow(
        column(6, selectInput("latent_shape", "Latent Node Shape:",
                              choices = c("circle", "square", "rectangle", "oval", "triangle", "diamond"),
                              selected = settings$last_latent_shape %||% "circle"
        )),
        column(6, colourpicker::colourInput("latent_color_input", "Latent Node Color:", value = settings$last_point_color_latent %||% "#cc3d3d"))
      ),
      fluidRow(
        column(6, selectInput("observed_shape", "Observed Node Shape:",
                              choices = c("circle", "square", "rectangle", "oval", "triangle", "diamond"),
                              selected = settings$last_observed_shape %||% "square"
        )),
        column(6, colourpicker::colourInput("observed_color_input", "Observed Node Color:", value = settings$last_point_color_observed %||% "#1262b3"))
      ),
      fluidRow(
        column(6, selectInput("int_shape", "Intercept Node Shape:",
                              choices = c("circle", "square", "rectangle", "oval", "triangle", "diamond"),
                              selected = settings$last_int_shape %||% "triangle"
        )),
        column(6, colourpicker::colourInput("int_color_input", "Intercept Node Color:", value = settings$last_point_color_int %||% "#0f993d"))
      ),
      fluidRow(
        column(6, numericInput("latent_size_input", "Latent Node Size:", value = settings$last_point_size_latent %||% 20, min = 1)),
        column(6, numericInput("observed_size_input", "Observed Node Size:", value = settings$last_point_size_observed %||% 12, min = 1))
      ),
      fluidRow(
        column(6, numericInput("int_size_input", "Intercept Node Size:", value = settings$last_point_size_int %||% 10, min = 1)),
        column(6, colourpicker::colourInput("node_border_color", "Node Border Color:", value = settings$last_node_border_color %||% "white"))
      ),
      fluidRow(
        column(6, numericInput("node_border_width", "Border Width:", value = settings$last_node_border_width %||% 1, min = 0.1, step = 0.1))
      ),
      hr(),
      fluidRow(
        conditionalPanel(
          condition = "input.latent_shape == 'rectangle' || input.latent_shape == 'oval' || input.latent_shape == 'diamond'",
          column(6, numericInput("width_height_ratio_latent", "Latent Node Width Ratio:", value = settings$last_width_height_ratio_latent %||% 1, min = 1))
        ),
        conditionalPanel(
          condition = "input.observed_shape == 'rectangle' || input.observed_shape == 'oval' || input.observed_shape == 'diamond'",
          column(6, numericInput("width_height_ratio_observed", "Observed Node Width Ratio:", value = settings$last_width_height_ratio_observed %||% 1.6, min = 1))
        ),
        conditionalPanel(
          condition = "input.int_shape == 'rectangle' || input.int_shape == 'oval' || input.int_shape == 'diamond'",
          column(6, numericInput("width_height_ratio_int", "Observed Node Width Ratio:", value = settings$last_width_height_ratio_int %||% 1.6, min = 1))
        )),
      fluidRow(
        column(
          12,
          selectInput("pre_palette", "Choose Color Palette",
                      choices = c("Earth Tones", "Vibrant Primary", "Cool Blues",
                                  "Warm Contrast", "Jewel Tones", "Pastel Soft",
                                  "Forest Green", "Sunset Warm",
                                  "Royal Contrast", "Forest Canopy", "Ocean Sunset",
                                  "Berry Garden", "Slate & Scarlet", "Autumn Rich",
                                  "Jewel Rich", "Nightfall & Ember"),
                      selected = "Earth Tones")
        ),
        column(12,
               div(
                 actionButton(
                   "apply_pre_palette",
                   class = "redo-button01",
                   label = tagList(icon("sync"), HTML("&nbsp;Update Color Values"))
                 ),
                 style = "display: flex; justify-content: center;"
               ))
      )
    )
  })

  outputOptions(output, "sem_nonselective_node", suspendWhenHidden = FALSE)


  output$sem_nonselective_edge <- renderUI({

    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(

      fluidRow(
        column(6, selectInput("lavaan_arrow_type", "Arrow Type:", choices = c("open", "closed"), selected = settings$last_lavaan_arrow_type %||% "closed")),
        column(6, numericInput("lavaan_arrow_size", "Arrow Size:", value = settings$last_lavaan_arrow_size %||% 0.1, min = 0.1, step = 0.1))
      ),
      fluidRow(
        column(6, numericInput("line_endpoint_spacing",
                               "Line Endpoint Spacing:",
                               value = settings$last_line_endpoint_spacing %||% 0.2, min = 0, step = 0.1)),
        column(6, selectInput("lavaan_arrow_location", "Arrowhead Location:",
                              choices = c("start", "end"), selected = settings$last_lavaan_arrow_location %||% "end"
        ))),
      fluidRow(
        column(6, colourpicker::colourInput("edge_color_input", "Edge Color:", value = settings$last_edge_color %||% "#000000")),
        column(6, numericInput("line_width_input",
                               "Linewidth:",
                               value = settings$last_line_width %||% 1, min = 0.1, step = 0.1)),
        column(6, numericInput("line_alpha_input", "Line Alpha:", value = settings$last_line_alpha %||% 1, min = 0, max = 1, step = 0.1))
      ),
      h5(HTML("<b style='font-size: 16px;'>Covariance (Two-way) Lines</b>")),
      fluidRow(
        column(
          6,
          numericInput(
            "lavaan_curved_x_shift",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_lavaan_curved_x_shift %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "lavaan_curved_y_shift",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_lavaan_curved_y_shift %||% 0,
            step = 0.1
          )
        )
      ),
      fluidRow(
        column(
          6,
          tags$span(
            icon("question-circle"),
            title = "Control the curvature magnitude of two-way (covariance) unlocked arrow(s).).",
            style = "cursor: help; margin-left: 6px; color: #007bff;"
          ),
          sliderInput(
            "lavaan_curvature_magnitude",
            "Curvature Size:",
            min = 0,
            max = 2,
            value = settings$last_lavaan_curvature_magnitude %||% 0.3,
            step = 0.01
          ),
        ),
        column(
          6,
          tags$span(
            icon("question-circle"),
            title = "Rotate the orientation of the two-way (covariance) unlocked arrow(s) by 180 degrees.",
            style = "cursor: help; margin-left: 6px; color: #007bff;"
          ),
          checkboxInput(
            "lavaan_rotate_curvature",
            "Rotate Curvature 180°",
            value = settings$last_lavaan_rotate_curvature %||% FALSE
          )
        )),
      fluidRow(
        column(
          6,
          tags$span(
            icon("question-circle"),
            title = "Control the asymmetry of two-way (covariance) unlocked arrow(s).).",
            style = "cursor: help; margin-left: 6px; color: #007bff;"
          ),
          sliderInput(
            "lavaan_curvature_asymmetry",
            "Curvature Asymmetry:",
            min = -1,
            max = 1,
            value = settings$last_lavaan_curvature_asymmetry %||% 0,
            step = 0.1
          ),
        ))

    )
  })

  outputOptions(output, "sem_nonselective_edge", suspendWhenHidden = FALSE)

  output$sem_nonselective_nodelabel <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      wellPanel(style = "background: #f8f9fa;",
                h5(HTML("<b style='font-size: 16px;'>Latent Node Labels</b>")),
                fluidRow(
                  column(6, numericInput("text_size_latent",
                                         "Text Size:",
                                         value = settings$last_text_size_latent %||% 18, min = 5, step = 1
                  )),
                  column(6, selectInput("text_font_latent", "Text Font:",
                                        choices = c("sans", "serif", "mono"), selected = settings$last_text_font_latent %||% "sans"
                  ))
                ),
                fluidRow(
                  column(6, colourpicker::colourInput("text_color_latent", "Text Color:", value = settings$last_text_color_latent %||% "#FFFFFF")),
                  column(6, numericInput("text_alpha_latent", "Text Alpha:", value = settings$last_text_alpha_latent %||% 1, min = 0, max = 1, step = 0.1))
                ),
                fluidRow(
                  column(6, selectInput("text_fontface_latent", "Fontface:", choices = c("plain", "bold", "italic"), selected = settings$last_text_fontface_latent %||% "plain"))
                )
      ),
      div(style = "margin-top: 10px;"),
      wellPanel(style = "background: #f8f9fa;",
                h5(HTML("<b style='font-size: 16px;'>Other Node Labels</b>")),
                fluidRow(
                  column(6, numericInput("text_size_others",
                                         "Text Size:",
                                         value = settings$last_text_size_others %||% 16, min = 5, step = 1
                  )),
                  column(6, selectInput("text_font_others", "Text Font:",
                                        choices = c("sans", "serif", "mono"), selected = settings$last_text_font_others %||% "sans"
                  ))
                ),
                fluidRow(
                  column(6, colourpicker::colourInput("text_color_others", "Text Color:", value = settings$last_text_color_others %||% "#FFFFFF")),
                  column(6, numericInput("text_alpha_others", "Text Alpha:", value = settings$last_text_alpha_others %||% 1, min = 0, max = 1, step = 0.1))
                ),
                fluidRow(
                  column(6, selectInput("text_fontface_others", "Fontface:", choices = c("plain", "bold", "italic"), selected = settings$last_text_fontface_others %||% "plain"))
                )
      )
    )
  })
  outputOptions(output, "sem_nonselective_nodelabel", suspendWhenHidden = FALSE)


  output$sem_nonselective_edgelabel <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6, numericInput("text_size_edges",
                               "Text Size:",
                               value = settings$last_text_size_edges %||% 14, min = 5, step = 1
        )),
        column(6, selectInput("text_font_edges", "Text Font:",
                              choices = c("sans", "serif", "mono"), selected = settings$last_text_font_edges %||% "sans"
        ))),
      fluidRow(
        column(6, selectInput("text_fontface_edges", "Fontface:", choices = c("plain", "bold", "italic"), selected = settings$last_text_fontface_edges %||% "plain")),
        column(6, colourpicker::colourInput("text_color_edges", "Text Color:", value = settings$last_text_color_edges %||% "#000000"))),
      fluidRow(
        column(6, numericInput("text_alpha_edges", "Text Alpha:", value = settings$last_text_alpha_edges %||% 1, min = 0, max = 1, step = 0.1)),
      ),
      fluidRow(
        column(6, checkboxInput("apply_fill_edges_color", "Fill Color for Texts", value = settings$last_apply_text_color_fill %||% TRUE)),
        conditionalPanel(
          condition = "input.apply_fill_edges_color",
          column(6, colourpicker::colourInput("text_fill_edges", "Filling Color:", value = settings$last_text_color_fill %||% "#FFFFFF")),
        )
      ),
      fluidRow(
        column(6, checkboxInput("remove_edgelabels", "Remove Edge Labels", value = settings$last_remove_edgelabels %||% FALSE))
      )
    )
  })

  outputOptions(output, "sem_nonselective_edgelabel", suspendWhenHidden = FALSE)

  output$sem_stats_label <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      conditionalPanel(
        condition = "output.is_grViz != true",
        checkboxInput(
          "annotate_sem_est",
          tags$b(style = "font-size: 16px;", "Annotate Parameter Estimates"),
          value = settings$last_annotate_sem_est %||% TRUE
        ),
        conditionalPanel(
          condition = "input.annotate_sem_est == true",
          wellPanel(
            style = "background: #f8f9fa;",
            helpText(HTML('Click <b>"Apply Changes"</b> to modify statistical annotations in your SEM diagram.')),
            fluidRow(
              column(6, checkboxInput("ustd_est", "Unstandardized estimates", value = settings$last_ustd_est %||% TRUE)),
              column(6, checkboxInput("std_est", "Standardized estimates", value = settings$last_std_est %||% FALSE)),
              column(6,
                     if (is_blavaan()) {
                       checkboxInput("ci_est", "Credible intervals", value = settings$last_conf_int %||% FALSE)
                     } else {
                       checkboxInput("ci_est", "Confidence intervals", value = settings$last_conf_int %||% FALSE)
                     }
              ),
              column(6,
                     if (is_blavaan()) {
                       checkboxInput("pval_est", "Bayesian significance (*)", value = settings$last_p_val %||% FALSE)
                     } else {
                       checkboxInput("pval_est", "Statistical significance (*)", value = settings$last_p_val %||% TRUE)
                     }
              )
            ),
            helpText(HTML('When only confidence/credible intervals are checked but not standardized/unstandardized estimates, unstandardized intervals are printed.'))
          )
        )
      ),
      h4("Path Highlighting (Per Group)"),
      checkboxInput(
        "highlight_free_path",
        tags$b(style = "font-size: 16px;", "Highlight Free/Fixed Paths (Per Group)"),
        value = settings$last_highlight_free_path %||% FALSE
      ),
      conditionalPanel(
        condition = "input.highlight_free_path",

        wellPanel(
          style = "background: #f8f9fa;",
          helpText(HTML('Click <b>"Apply Changes"</b> to modify aesthetics of free and fixed paths per group.')),
          fluidRow(
            column(6, colourpicker::colourInput("free_path_color", "Free Path Color:", value = settings$last_free_path_color %||% "#22327D")),
            column(6, colourpicker::colourInput("fixed_path_color", "Fixed Path Color:", value = settings$last_fixed_path_color %||% "#BCCCE0")),
            column(6, selectInput("free_label_fontface", "Free Label Fontface:", choices = c("plain", "bold", "italic")), selected = settings$last_free_label_fontface %||% "plain"),
            column(6, selectInput("fixed_label_fontface", "Fixed Label Fontface:", choices = c("plain", "bold", "italic")), selected = settings$last_fixedlabel_fontface %||% "plain")
          ),
          helpText("Free - Estimated parameters; Fixed - Non-estimated parameters"),
        )
      ),
      checkboxInput(
        "highlight_sig_path",
        tags$b(style = "font-size: 16px;", "Highlight Significant Paths (Per Group)"),
        value = settings$last_highlight_sig_path %||% FALSE
      ),
      conditionalPanel(
        condition = "input.highlight_sig_path",
        wellPanel(
          style = "background: #f8f9fa;",
          helpText(HTML('Click <b>"Apply Changes"</b> to modify aesthetics of statistically significant and insignificant paths per group.')),
          fluidRow(
            column(6, colourpicker::colourInput("sig_path_color", "Sig. Path Color:", value = settings$last_sig_path_color %||% "#000000")),
            column(6, colourpicker::colourInput("non_sig_path_color", "Non. Sig. Path Color:", value = settings$last_non_sig_path_color %||% "#C5C6D8")),
            column(6, selectInput("sig_label_fontface", "Sig. Label Fontface:", choices = c("plain", "bold", "italic")), selected = settings$last_sig_label_fontface %||% "plain"),
            column(6, selectInput("non_sig_label_fontface", "Non. Sig. Label Fontface:", choices = c("plain", "bold", "italic")), selected = settings$last_non_sig_label_fontface %||% "plain")
          ),
          fluidRow(
            column(6, if (!is_blavaan()) {numericInput("p_val_alpha", "P-value alpha (*)", value = settings$last_p_val_alpha %||% 0.05)})
          ),
        )
      )
    )
  })

  outputOptions(output, "sem_stats_label", suspendWhenHidden = FALSE)

  output$param_node_aesthetics <- renderUI({
    values$force_ui_refresh

    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_node_color", "Node Color:", value = settings$last_modify_params_node_color %||% "#2F6391")),
        column(6, numericInput("modify_params_node_alpha", "Node Alpha:", value = settings$last_modify_params_node_alpha %||% 1, min = 0, max = 1, step = 0.1))),
      fluidRow(
        column(6, selectInput("modify_params_node_shape", "Node Shape:",
                              choices = c("circle", "square", "rectangle", "oval", "triangle", "diamond"),
                              selected = settings$last_modify_params_node_shape %||% "circle")),
        column(6, numericInput("modify_params_node_size", "Node Size:", value = settings$last_modify_params_node_size %||% 20, min = 1))),
      conditionalPanel(
        condition = "input.modify_params_node_shape == 'rectangle' || input.modify_params_node_shape == 'oval' || input.modify_params_node_shape == 'diamond'",
        fluidRow(
          column(
            6,
            div(
              numericInput(
                "modify_params_node_width_height_ratio",
                label = HTML(paste(
                  icon("ruler-combined", style = "margin-right: 8px;"), "Width/Height Ratio"
                )),
                value = settings$last_modify_params_node_width_height_ratio %||% 1.6,
                min = 0.1,
                step = 0.1
              ),
              tags$span(
                icon("question-circle"),
                title = "Adjust the ratio of width to height for rectangle, oval, and diamond shapes.",
                style = "cursor: help; margin-left: 6px; color: #007bff;"
              ),
              style = "display: flex; align-items: center;"
            )
          ))
      ),
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_node_border_color", "Node Border Color:", value = settings$last_modify_params_node_border_color %||% "white")),
        column(6, numericInput("modify_params_node_border_width", "Node Border Width:", value = settings$last_modify_params_node_border_width %||% 1, min = 0.1, step = 0.1))
      )
    )
  })

  outputOptions(output, "param_node_aesthetics", suspendWhenHidden = FALSE)

  output$param_edge_aesthetics <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})
    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_edge_color", "Edge Color:",
                                            value = settings$last_modify_params_edge_color %||% "#cc3d3d")),
        column(6, selectInput("modify_params_edge_color_type", "Line Color Type:", choices = c("Single", "Gradient"),
                              selected = settings$last_modify_params_edge_color_type %||% "Single")),
        column(6, numericInput("modify_params_edge_linewidth",
                               "Linewidth:", value = settings$last_modify_params_edge_linewidth %||% 1,
                               min = 0.1, step = 0.1)),
        column(6, numericInput("modify_params_edge_alpha", "Edge Alpha:",
                               value = settings$last_modify_params_edge_alpha %||% 1,
                               min = 0, max = 1, step = 0.1)),
        column(6, selectInput("modify_params_edge_line_style", "Edge Line Style:",
                              choices = c("solid", "dashed", "dotted"),
                              selected = settings$last_modify_params_edge_line_style %||% "solid"))),
      conditionalPanel(
        condition = "input.modify_params_edge_color_type == 'Gradient'",
        fluidRow(
          column(6, colourpicker::colourInput("modify_params_edge_end_color", "Edge End Color:",
                                              value = settings$last_modify_params_edge_end_color %||% "#1262b3")),
          column(6, sliderInput("modify_params_edge_gradient_position", "Gradient Intersection:",
                                min = 0.01, max = 0.99,
                                value = settings$last_modify_params_edge_gradient_position %||% 0.5, step = 0.01),
                 tags$span(
                   icon("question-circle"),
                   title = "The close to 0, the more gradient favors the end color.",
                   style = "cursor: help; margin-left: 6px; color: #007bff;"
                 ),
                 style = "display: flex; align-items: center;"
          )
        )
      )
    )
  })

  outputOptions(output, "param_edge_aesthetics", suspendWhenHidden = FALSE)

  output$param_node_xy <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})
    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_node_shift",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Shifts to 0"),
                 style = "display: flex; align-items: center; justify-content: center; gap: 10px;"
               )
        )),  # Dynamic dropdown
      fluidRow(
        column(
          6,
          numericInput(
            "sem_node_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_node_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "sem_node_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_node_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_node_xy", suspendWhenHidden = FALSE)

  output$param_latent_node_xy <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})
    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_latent_node_shift",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Shifts to 0"),
                 style = "display: flex; align-items: center; justify-content: center; gap: 10px;"
               )
        )),  # Dynamic dropdown
      fluidRow(
        column(
          6,
          numericInput(
            "sem_latent_node_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_latent_node_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "sem_latent_node_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_latent_node_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_latent_node_xy", suspendWhenHidden = FALSE)

  output$param_latent_node_angle <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})
    tagList(
      fluidRow(
        column(
          6,
          numericInput(
            "sem_latent_node_angle",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Orientation Shift (Degree)")),
            value = settings$last_modify_params_latent_node_angle_value %||% 0,
            step = 0.1
          )
        ),
        column(6,
               actionButton(
                 "reset_latent_node_angle",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset Rotation Value to 0"),
                 style = "display: flex; align-items: center; justify-content: center; gap: 10px;"
               )
        )
      )
    )
  })

  outputOptions(output, "param_latent_node_xy", suspendWhenHidden = FALSE)

  output$param_cov_edge <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6, tags$span(
          icon("question-circle"),
          title = "Control the curvature magnitude of two-way (covariance) unlocked arrow.).",
          style = "cursor: help; margin-left: 6px; color: #007bff;"
        ), sliderInput(
          "param_cov_edge_curvature_magnitude",
          "Curvature Size:",
          min = 0,
          max = 2,
          value = settings$last_modify_params_cov_edge_curvature_magnitude %||% 0.3,
          step = 0.01)),
        column(6,
               tags$span(
                 icon("question-circle"),
                 title = "Rotate the orientation of the two-way (covariance) unlocked arrow by 180 degrees.",
                 style = "cursor: help; margin-left: 6px; color: #007bff;"
               ),
               checkboxInput("param_cov_edge_rotate_curvature", "Rotate Curvature 180°",
                             value = settings$last_modify_params_cov_edge_rotate_curvature %||% FALSE)
        )),
      fluidRow(
        column(6, tags$span(
          icon("question-circle"),
          title = "Control the asymmetry of two-way (covariance) unlocked arrow.).",
          style = "cursor: help; margin-left: 6px; color: #007bff;"
        ), sliderInput(
          "param_cov_edge_curvature_asymmetry",
          "Curvature Asymmetry:",
          min = -1,
          max = 1,
          value = settings$last_modify_params_cov_edge_curvature_asymmetry %||% 0,
          step = 0.1))
      ),
      hr(),
      helpText('Set relative positions of edges by specifying the XY shifts.'),
      fluidRow(
        column(6,
               actionButton(
                 "reset_edge_cov_shift",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Values to 0")
               )
        )
      ),
      fluidRow(
        column(
          6,
          numericInput(
            "param_cov_edge_x_shift",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_cov_edge_x_shift %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "param_cov_edge_y_shift",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_cov_edge_y_shift %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_cov_edge", suspendWhenHidden = FALSE)

  output$param_edge_xy <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_edge_shift",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Values to 0")
               )
        )),  # Dynamic dropdown
      helpText('Set absolute positions of edges by specifying the coordinates.'),
      fluidRow(
        column(
          6,
          numericInput(
            "modify_params_edge_start_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Coordinate X (Start)")),
            value = settings$last_modify_params_edge_x_start %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "modify_params_edge_start_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Coordinate Y (Start)")),
            value = settings$last_modify_params_edge_y_start %||% 0,
            step = 0.1
          )
        )
      ),
      fluidRow(
        column(
          6,
          numericInput(
            "modify_params_edge_end_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Coordinate X (End)")),
            value = settings$last_modify_params_edge_x_end %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "modify_params_edge_end_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Coordinate Y (End)")),
            value = settings$last_modify_params_edge_y_end %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_edge_xy", suspendWhenHidden = FALSE)


  output$param_nodelabel <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_nodelabel_color", "Text Color:",
                                            value = settings$last_modify_params_nodelabel_color %||% "#FFFFFF")),
        column(6, numericInput("modify_params_nodelabel_size",
                               "Text Size:",
                               value = settings$last_modify_params_nodelabel_size %||% 18, min = 5, step = 1)),
        column(6, numericInput("modify_params_nodelabel_alpha", "Text Alpha:",
                               value = settings$last_modify_params_nodelabel_alpha %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, numericInput("modify_params_nodelabel_angle", "Angle (deg):",
                               value = settings$last_modify_params_nodelabel_angle %||% 0, min = -180, max = 180)),
        column(6, selectInput("modify_params_nodelabel_font", "Font:", choices = c("sans", "mono", "serif"),
                              selected = settings$last_modify_params_nodelabel_font %||% "sans")),
        column(6, selectInput("modify_params_nodelabel_fontface", "Font Face:", choices = c("plain", "bold", "italic")),
               selected = settings$last_modify_params_nodelabel_fontface %||% "plain")
      )
    )
  })

  outputOptions(output, "param_nodelabel", suspendWhenHidden = FALSE)


  output$param_nodelabel_xy <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_nodelabel_shift",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Shifts to 0")
               )
        )),
      fluidRow(
        column(
          6,
          numericInput(
            "modify_params_nodelabel_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_nodelabel_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "modify_params_nodelabel_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_nodelabel_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_nodelabel_xy", suspendWhenHidden = FALSE)


  output$param_edgelabel <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_edgelabel_color", "Text Color:",
                                            value = settings$last_modify_params_edgelabel_color %||% "#cc3d3d")),
      ),
      fluidRow(
        column(6, checkboxInput("apply_modify_params_edgelabel_fill", "Fill Color for Texts", value = settings$last_apply_modify_params_edgelabel_fill %||% TRUE)),
        conditionalPanel(
          condition = "input.apply_modify_params_edgelabel_fill",
          column(6, colourpicker::colourInput("modify_params_edgelabel_fill", "Text Fill:",
                                              value = settings$last_modify_params_edgelabel_fill %||% "#FFFFFF"))
        )
      ),
      fluidRow(
        column(6, numericInput("modify_params_edgelabel_size",
                               "Text Size:",
                               value = settings$last_modify_params_edgelabel_size %||% 14,
                               min = 5, step = 1)),
        column(6, numericInput("modify_params_edgelabel_alpha", "Text Alpha:",
                               value = settings$last_modify_params_edgelabel_alpha %||% 1,
                               min = 0, max = 1, step = 0.1))
      ),
      fluidRow(
        column(6, numericInput("modify_params_edgelabel_angle", "Angle (deg):",
                               value = settings$last_modify_params_edgelabel_angle %||% 0,
                               min = -180, max = 180)),
        column(6, selectInput("modify_params_edgelabel_font", "Font:", choices = c("sans", "mono", "serif"),
                              selected = settings$last_modify_params_edgelabel_font %||% "sans")),
        column(6, selectInput("modify_params_edgelabel_fontface", "Font Face:", choices = c("plain", "bold", "italic")),
               selected = settings$last_modify_params_edgelabel_fontface %||% "plain")
      )
    )
  })

  outputOptions(output, "param_edgelabel", suspendWhenHidden = FALSE)

  output$param_edgelabel_xy <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_edgelabel_xy_shift",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Shifts to 0")
               )
        )),
      fluidRow(
        column(
          6,
          numericInput(
            "modify_params_edgelabel_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_edgelabel_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "modify_params_edgelabel_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_edgelabel_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_edgelabel_xy", suspendWhenHidden = FALSE)

  output$show_residuals <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    checkboxInput("residuals", tagList(
      icon("arrow-rotate-left", style = "margin-right: 8px; color: #28a745;"),
      tags$b(style = "font-size: 16px;", "Show Residual Variances")
    ), value = settings$last_residuals %||% FALSE)
  })

  outputOptions(output, "show_residuals", suspendWhenHidden = FALSE)

  output$sem_nonselective_loop <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6, numericInput("lavaan_radius", "Radius:", value = settings$last_lavaan_radius %||% 2.5, min = 0.1)),
        column(6, numericInput("lavaan_width_loop", "Loop Width:", value = settings$last_lavaan_width_loop %||% 1, min = 0.1)),
      ),
      fluidRow(
        column(6, colourpicker::colourInput("lavaan_line_color_loop", "Line Color:", value = settings$last_lavaan_line_color_loop %||% "#000000")),
        column(6, numericInput("lavaan_line_alpha_loop", "Line Alpha:", value = settings$last_lavaan_line_alpha_loop %||% 1, min = 0, max = 1, step = 0.1))
      ),
      fluidRow(
        column(6, selectInput("lavaan_arrow_type_loop", "Arrow Type:", choices = c("open", "closed"), selected = settings$last_lavaan_arrow_type_loop %||% "closed")),
        column(6, numericInput("lavaan_arrow_size_loop", "Arrow Size:", value = settings$last_lavaan_arrow_size_loop %||% 0.1, min = 0.1, step = 0.1))
      ),
      fluidRow(
        column(6, numericInput("lavaan_gap_size_loop", "Gap Size:", value = settings$last_lavaan_gap_size_loop %||% 0.2, min = 0, max = 1, step = 0.05)),
        column(6, numericInput("lavaan_height_loop", "Loop Height:", value = settings$last_lavaan_height_loop %||% 1, min = 0.1))
      ),
      fluidRow(
        column(12, checkboxInput("lavaan_two_way_arrow_loop", "Two-way Arrow", value = settings$last_lavaan_two_way_arrow_loop %||% TRUE))
      ),
      fluidRow(
        column(6, numericInput("lavaan_loop_offset",
                               "Offset from Nodes:",
                               value = settings$last_lavaan_loop_offset %||% 2, min = 0, step = 0.1)),
        column(6, column(6, selectInput("residuals_orientation_type", "Orientation Type:", choices = c("Graded", "Quadratic"),
                                        selected = settings$last_residuals_orientation_type %||% "Graded")))
      )
    )
  })

  outputOptions(output, "sem_nonselective_loop", suspendWhenHidden = FALSE)

  output$param_loop_aesthetics <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_loop_color", "Loop Color:",
                                            value = settings$last_modify_params_loop_color %||% "#000000")),
        column(6, numericInput("modify_params_loop_alpha", "Loop Alpha:",
                               value = settings$last_modify_params_loop_alpha %||% 1, min = 0, max = 1, step = 0.1))
      ),
      fluidRow(
        column(6, numericInput("modify_params_loop_radius", "Loop Radius:",
                               value = settings$last_modify_params_loop_radius %||% 2.5, min = 0.5, step = 0.1)),
        column(6, numericInput("modify_params_loop_width", "Loop Width:",
                               value = settings$last_modify_params_loop_width %||% 1, min = 0.1, step = 0.1))
      ),
      fluidRow(
        column(6, selectInput("modify_params_loop_type", "Arrow Type:",
                              choices = c("closed", "open"),
                              selected = settings$last_modify_params_loop_type %||% "closed")),
        column(6, numericInput("modify_params_loop_arrow_size", "Arrow Size:",
                               value = settings$last_modify_params_loop_arrow_size %||% 0.1, min = 0.1, step = 0.1))
      ),
      fluidRow(
        column(6, numericInput("modify_params_loop_gap_size", "Gap Size:",
                               value = settings$last_modify_params_loop_gap_size %||% 0.05, min = 0, max = 0.5, step = 0.01)),
        column(6, checkboxInput("modify_params_loop_two_way", "Two-way Arrow",
                                value = settings$last_modify_params_loop_two_way %||% TRUE))
      )
    )
  })

  outputOptions(output, "param_loop_aesthetics", suspendWhenHidden = FALSE)

  # Loop Position Shift UI
  output$param_loop_xy <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_loop_shift",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Shifts to 0"),
                 style = "display: flex; align-items: center; justify-content: center; gap: 10px;"
               )
        )
      ),
      fluidRow(
        column(
          6,
          numericInput(
            "sem_loop_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_loop_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "sem_loop_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_loop_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_loop_xy", suspendWhenHidden = FALSE)

  # Loop Location UI
  output$param_loop_location <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(12,
               numericInput("modify_params_loop_location_value", "Location Relative to Node(s) (0-360°):",
                            value = settings$last_modify_params_loop_location_value %||% 0,
                            min = 0, max = 360, step = 1),

               div(style = "margin-bottom: 10px;",
                   fluidRow(
                     column(3, actionButton("loop_angle_right", "\u2192 (0°)",
                                            class = "redo-button01", width = "90%")),
                     column(3, actionButton("loop_angle_top_right", "\u2197 (45°)",
                                            class = "redo-button01", width = "90%")),
                     column(3, actionButton("loop_angle_top", "\u2191 (90°)",
                                            class = "redo-button01", width = "90%")),
                     column(3, actionButton("loop_angle_top_left", "\u2196 (135°)",
                                            class = "redo-button01", width = "90%"))
                   ),
                   fluidRow(
                     column(3, actionButton("loop_angle_left", "\u2190 (180°)",
                                            class = "redo-button01", width = "90%")),
                     column(3, actionButton("loop_angle_bottom_left", "\u2199 (225°)",
                                            class = "redo-button01", width = "90%")),
                     column(3, actionButton("loop_angle_bottom", "\u2193 (270°)",
                                            class = "redo-button01", width = "90%")),
                     column(3, actionButton("loop_angle_bottom_right", "\u2198 (315°)",
                                            class = "redo-button01", width = "90%"))
                   )
               )
        )
      )
    )
  })

  outputOptions(output, "param_loop_location", suspendWhenHidden = FALSE)

  # Loop Label Styling UI
  output$param_looplabel <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_looplabel_color", "Text Color:",
                                            value = settings$last_modify_params_looplabel_color %||% "#000000")),
        column(6, numericInput("modify_params_looplabel_size", "Text Size:",
                               value = settings$last_modify_params_looplabel_size %||% 14, min = 5, step = 1)),
        fluidRow(
          column(6, checkboxInput("apply_modify_params_looplabel_fill", "Fill Color for Texts", value = settings$last_apply_modify_params_edgelabel_fill %||% TRUE)),
          conditionalPanel(
            condition = "input.apply_modify_params_looplabel_fill",
            column(6, colourpicker::colourInput("modify_params_looplabel_fill", "Text Fill:",
                                                value = settings$last_modify_params_looplabel_fill %||% "#FFFFFF"))
          )
        ),
        column(6, numericInput("modify_params_looplabel_alpha", "Text Alpha:",
                               value = settings$last_modify_params_looplabel_alpha %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, numericInput("modify_params_looplabel_angle", "Angle (deg):",
                               value = settings$last_modify_params_looplabel_angle %||% 0, min = -180, max = 180)),
        column(6, selectInput("modify_params_looplabel_font", "Font:",
                              choices = c("sans", "mono", "serif"),
                              selected = settings$last_modify_params_looplabel_font %||% "sans")),
        column(6, selectInput("modify_params_looplabel_fontface", "Font Face:",
                              choices = c("plain", "bold", "italic"),
                              selected = settings$last_modify_params_looplabel_fontface %||% "plain"))
      )
    )
  })

  outputOptions(output, "param_looplabel", suspendWhenHidden = FALSE)

  # Loop Label Position Shift UI
  output$param_looplabel_xy <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_looplabel_shift",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Shifts to 0"),
                 style = "display: flex; align-items: center; justify-content: center; gap: 10px;"
               )
        )
      ),
      fluidRow(
        column(
          6,
          numericInput(
            "sem_looplabel_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_looplabel_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "sem_looplabel_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_looplabel_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_looplabel_xy", suspendWhenHidden = FALSE)

  output$highlight_multi_group <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    checkboxInput(
      "highlight_multi_group",
      tags$b(style = "font-size: 16px;", "Highlight Group Differences"),
      value = settings$last_highlight_multi_group %||% FALSE
    )
  })

  outputOptions(output, "highlight_multi_group", suspendWhenHidden = FALSE)

  output$highlight_multi_group_edges <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      checkboxInput(
        "highlight_multi_group_edges",
        tags$b(style = "font-size: 14px;", "Significant Edges & Labels Aesthetics"),
        value = settings$last_highlight_multi_group_edges %||% FALSE
      ),
      conditionalPanel(
        condition = "input.highlight_multi_group_edges",
        fluidRow(
          column(6, colourpicker::colourInput("sig_diff_path_color", "Sig. Path Color:",
                                              value = settings$last_sig_diff_path_color %||% "#cc3d3d")),
          column(6, numericInput("sig_diff_path_line_width", "Sig. Path Width:",
                                 value = settings$last_sig_diff_path_line_width %||% 1,
                                 min = 0.1, step = 0.1)),
          column(6, selectInput("sig_diff_edgelabel_fontface", "Font Face:",
                                choices = c("plain", "bold", "italic"),
                                selected = settings$last_sig_diff_edgelabel_fontface %||% "bold")) # Bold for emphasis
        )
      )
    )
  })

  outputOptions(output, "highlight_multi_group_edges", suspendWhenHidden = FALSE)

  output$highlight_free_path_multi_group <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    checkboxInput(
      "highlight_free_path_multi_group",
      tags$b(style = "font-size: 16px;", "Highlight Multi-Group Invariance"),
      value = settings$last_highlight_free_path_multi_group %||% FALSE
    )
  })

  outputOptions(output, "highlight_free_path_multi_group", suspendWhenHidden = FALSE)

  output$highlight_free_path_multi_group_invariance <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      checkboxInput(
        "highlight_free_path_multi_group_invariance",
        tags$b(style = "font-size: 14px;", "Invariance Aesthetics"),
        value = settings$last_highlight_free_path_multi_group_invariance %||% FALSE
      ),
      conditionalPanel(
        condition = "input.highlight_free_path_multi_group_invariance",
        helpText("Invariance constraints (estimated parameters forced equal across groups)"),
        fluidRow(
          column(6, colourpicker::colourInput("invariance_color", "Color:",
                                              value = settings$last_invariance_color %||% "#5C4585")),
          column(6, numericInput("invariance_line_width", "Width:",
                                 value = settings$last_invariance_line_width %||% 1.5,
                                 min = 0.1, step = 0.1))
        ),
        fluidRow(
          column(6, selectInput("invariance_fontface", "Label:",
                                choices = c("plain", "bold", "italic"),
                                selected = settings$last_invariance_fontface %||% "bold"))
        ),
      )
    )
  })

  outputOptions(output, "highlight_free_path_multi_group_invariance", suspendWhenHidden = FALSE)

  output$highlight_free_path_multi_group_difference <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    tagList(
      checkboxInput(
        "highlight_free_path_multi_group_difference",
        tags$b(style = "font-size: 14px;", "Group-Specific Aesthetics"),
        value = settings$last_highlight_free_path_multi_group_difference %||% FALSE
      ),
      conditionalPanel(
        condition = "input.highlight_free_path_multi_group_difference",
        helpText("Group differences (estimated parameters that vary across groups)"),
        fluidRow(
          column(6, colourpicker::colourInput("group_diff_color", "Color:",
                                              value = settings$last_group_diff_color %||% "#CC8966")),
          column(6, numericInput("group_diff_line_width", "Width:",
                                 value = settings$last_group_diff_line_width %||% 1,
                                 min = 0.1, step = 0.1))
        ),
        fluidRow(
          column(6, selectInput("group_diff_fontface", "Label:",
                                choices = c("plain", "bold"),
                                selected = settings$last_group_diff_fontface %||% "bold"))
        )
      )
    )
  })

  outputOptions(output, "highlight_free_path_multi_group_difference", suspendWhenHidden = FALSE)

  output$network_layout <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    safe_random_seed <- tryCatch({
      if (!is.null(bundles$random_seed)) {
        bundles$random_seed
      } else {
        123  # default value
      }
    }, error = function(e) {
      123  # default value on error
    })

    layout_choices <- c(
      "Fruchterman-Reingold" = "fr",
      "Kamada-Kawai" = "kk",
      "Circular Layout" = "circle",
      "Grid Layout" = "grid",
      "Random Layout" = "random",
      "Dimensionality Reduction" = "dim_reduction"
    )

    ai_settings <- list(
      gemini = input$gemini_api_key,
      openai = input$openai_api_key,
      mistral = input$mistral_api_key,
      claude = input$claude_api_key,
      ollama_model = input$ollama_model
    )

    if (any(nzchar(unlist(ai_settings[c("gemini","openai","mistral","claude")])))) {
      layout_choices <- c(layout_choices, "GenAI" = "layout_ai")
    }

    tagList(
      fluidRow(
        column(12,
               conditionalPanel(
                 condition = "output.is_bipartite_or_ggnet2_or_qgraph != true",
                 selectInput(
                   "layout_method",
                   "Choose Layout Method:",
                   choices = layout_choices,
                   selected = settings$last_layout_method %||% "fr")
               )
        ),
      ),
      fluidRow(
        column(6, checkboxInput("is_directed", "Directed Network", value = settings$last_is_directed %||% TRUE)),

        column(6, div(style = "display: flex; align-items: center;",
                      numericInput("random_seed", "Set Random Seed",
                                   value = settings$last_random_seed %||% safe_random_seed, min = 1, step = 1)
        ))
      ),
      fluidRow(
        column(6, numericInput("x_center_net", "X Center:", value = settings$last_x_center %||% 0, step = 1)),
        column(6, numericInput("y_center_net", "Y Center:", value = settings$last_y_center %||% 0, step = 1))
      ),
      fluidRow(
        column(6, numericInput("layout_x_net", "Layout Width (X):", value = settings$last_layout_x %||% 30, min = 0.1, step = 0.1)),
        column(6, numericInput("layout_y_net", "Layout Height (Y):", value = settings$last_layout_y %||% 30, min = 0.1, step = 0.1))
      ),
      checkboxInput(
        "flip_network_layout",
        tagList(
          icon("retweet", style = "margin-right: 8px; color: #FF6B6B;"),
          tags$b(style = "font-size: 16px;", "Flip Network Layout"),
        ),
        value = settings$last_flip_network_layout %||% FALSE
      ),
      conditionalPanel(
        condition = "input.flip_network_layout",
        selectInput("flip_network_layout_direction", "Flip Direction",
                    choices = c("Relative to Horizontal" = "horizontal",
                                "Relative to Vertical" = "vertical",
                                "Relative to Horizontal and Vertical" = "both"),
                    selected = settings$last_flip_network_layout_direction %||%  "horizontal")
      ),
      checkboxInput(
        "rotate_network_layout",
        tagList(
          icon("sync", style = "margin-right: 8px; color: #3F51B5;"),
          tags$b(style = "font-size: 16px;", "Rotate Network Layout"),
        ),
        value = settings$last_rotate_network_layout %||% FALSE
      ),
      conditionalPanel(
        condition = "input.rotate_network_layout",
        fluidRow(
          column(
            12,
            numericInput(
              "rotate_network_layout_angle",
              label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Orientation (Degree)")),
              value = settings$last_rotate_network_layout_angle %||% 0,
              step = 0.1
            )
          )
        )
      ),
      conditionalPanel(
        condition = "input.layout_method == 'dim_reduction'",
        fluidRow(
          column(12, selectInput(
            "dim_reduction_method",
            "Dimensionality Reduction Method:",
            choices = c(
              "t-SNE" = "tsne",
              "UMAP" = "umap",
              "PCA" = "pca"
            ),
            selected = settings$last_dim_reduction_method %||% "tsne"
          ))
        )),
      conditionalPanel(
        condition = "input.layout_method == 'layout_ai'",
        fluidRow(
          column(width = 12,
                 textAreaInput(
                   "additional_prompts_network_layout",
                   "Additional prompts for layout",
                   value = "",
                   placeholder = "Write additional prompts (under 30 words).",
                   width = "100%",
                   height = "100px"
                 )
          )
        )
      )
    )
  })

  outputOptions(output, "network_layout", suspendWhenHidden = FALSE)

  output$qgraph_global_node <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    checkboxInput(
      "nonselective_modify_node_network_qgraph",
      tagList(
        icon("paint-brush", style = "margin-right: 8px; color: #1262b3;"),
        tags$b(style = "font-size: 16px;", "Apply Global Nodes Aesthetics (qgraph)"),
      ),
      value = settings$last_apply_global_nodes %||% FALSE
    )
  })

  outputOptions(output, "qgraph_global_node", suspendWhenHidden = FALSE)

  output$qgraph_global_edge <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    checkboxInput(
      "nonselective_modify_edge_network_qgraph",
      tagList(
        icon("paint-brush", style = "margin-right: 8px; color: #1262b3;"),
        tags$b(style = "font-size: 16px;", "Apply Global Edges Aesthetics (qgraph)"),
      ),
      value = settings$last_apply_global_edges %||% FALSE
    )
  })

  outputOptions(output, "qgraph_global_edge", suspendWhenHidden = FALSE)


  output$qgraph_global_annotation <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    checkboxInput(
      "nonselective_modify_annotation_network_qgraph",
      tagList(
        icon("paint-brush", style = "margin-right: 8px; color: #1262b3;"),
        tags$b(style = "font-size: 16px;", "Apply Global Annotation Aesthetics (qgraph)"),
      ),
      value = FALSE
    )
  })

  outputOptions(output, "qgraph_global_annotation", suspendWhenHidden = FALSE)

  output$change_group_node_bipartite <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    checkboxInput(
      "change_group_node_bipartite",
      tagList(
        icon("paint-brush", style = "margin-right: 8px; color: #1262b3;"),
        tags$b(style = "font-size: 16px;", "Change Target Group For Nodes Aesthetics (Bipartite)"),
      ),
      value = settings$last_apply_bipartite_nodes %||% FALSE
    )
  })

  outputOptions(output, "change_group_node_bipartite", suspendWhenHidden = FALSE)

  output$change_group_edge_bipartite <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    checkboxInput(
      "change_group_edge_bipartite",
      tagList(
        icon("paint-brush", style = "margin-right: 8px; color: #1262b3;"),
        tags$b(style = "font-size: 16px;", "Change Target Group for Edges Aesthetics (Bipartite)"),
      ),
      value = settings$last_apply_bipartite_edges %||% FALSE
    )
  })

  outputOptions(output, "change_group_edge_bipartite", suspendWhenHidden = FALSE)

  output$change_group_annotation_bipartite <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    checkboxInput(
      "change_group_annotation_bipartite",
      tagList(
        icon("paint-brush", style = "margin-right: 8px; color: #1262b3;"),
        tags$b(style = "font-size: 16px;", "Change Target Group For Annotations Aesthetics (Bipartite)"),
      ),
      value = settings$last_apply_bipartite_annotations %||% FALSE
    )
  })

  outputOptions(output, "change_group_annotation_bipartite", suspendWhenHidden = FALSE)

  output$network_node_aesthetics <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})


    tagList(
      fluidRow(
        column(6, selectInput("node_shape_net",
                              HTML(paste(
                                "Select Shape"
                              )),
                              choices = c("circle", "square", "rectangle", "oval", "triangle", "diamond"),
                              selected = settings$last_node_shape %||% "circle"
        )),
        conditionalPanel(
          condition = "input.node_shape_net == 'rectangle' || input.node_shape_net == 'oval' || input.node_shape_net == 'diamond'",
          column(
            6,
            div(
              numericInput(
                "node_width_height_ratio_net",
                label = HTML(paste(
                  icon("ruler-combined", style = "margin-right: 8px;"), "Width/Height Ratio"
                )),
                value = settings$last_node_width_height_ratio %||% 1.6,
                min = 0.1,
                step = 0.1
              ),
              tags$span(
                icon("question-circle"),
                title = "Adjust the ratio of width to height for rectangle, oval, and diamond shapes.",
                style = "cursor: help; margin-left: 6px; color: #007bff;"
              ),
              style = "display: flex; align-items: center;"
            )
          )
        )),
      fluidRow(
        column(6, numericInput("node_size_net", "Node Size:", value = settings$last_node_size %||% 15, step = 1)),
        column(6, colourpicker::colourInput("node_fill_color_net", "Node Fill Color:", value = settings$last_node_fill_color %||% "#1262b3"))
      ),
      checkboxInput(
        "node_fill_color_net_gradient",
        "Gradient Node Fill",
        value = settings$last_node_fill_color_net_gradient %||% FALSE
      ),
      conditionalPanel(
        condition = "input.node_fill_color_net_gradient",
        helpText("Color network nodes in a specific group with gradient color schemes"),
        fluidRow(
          column(6, colourpicker::colourInput("grad_start_color_net", "Gradient Start Color:", value = settings$last_grad_start_color_net %||% "blue")),
          column(6, colourpicker::colourInput("grad_end_color_net", "Gradient End Color:", value = settings$last_grad_end_color_net %||% "red")),
          column(6, sliderInput("gradient_position_points_net", "Gradient Intersection:", min = 0.01, max = 0.99,
                                value = settings$last_gradient_position_points_net %||% 0.5, step = 0.01),
                 tags$span(
                   icon("question-circle"),
                   title = "The close to 0, the more gradient favors the end color.",
                   style = "cursor: help; margin-left: 6px; color: #007bff;"
                 ),
                 style = "display: flex; align-items: center;"
          )
        )
      ),
      fluidRow(
        column(6, colourpicker::colourInput("node_border_color_net", "Node Border Color:", value = settings$last_node_border_color %||% "#FFFFFF")),
        column(6, numericInput("node_border_width_net", "Node Border Width:", value = settings$last_node_border_width %||% 1, step = 0.1))
      ),
      fluidRow(
        column(6, numericInput("node_alpha_net", "Node Alpha:", value = settings$last_node_alpha %||% 1, min = 0, max = 1, step = 0.1))
      ),
      fluidRow(
        column(12, checkboxInput("use_clustering", "Enable Clustering", value = settings$last_use_clustering %||% FALSE))
      ),
      conditionalPanel(
        condition = "input.use_clustering == true",
        fluidRow(
          column(12, selectInput(
            "clustering_method",
            "Clustering Method:",
            choices = c(
              "Louvain (undirected)" = "louvain",
              "Leiden (undirected)" = "leiden",
              "Walktrap (directed/undirected)" = "walktrap",
              "Fast Greedy (undirected)" = "fast_greedy"
            ),
            selected = settings$last_clustering_method %||% "louvain"
          )),
          column(12, selectInput(
            "cluster_palette",
            "Select Color Palette:",
            choices = c(
              "Rainbow" = "rainbow",
              "Set1" = "Set1",
              "Paired" = "Paired",
              "Dark2" = "Dark2",
              "Set3" = "Set3",
              "Pastel1" = "Pastel1",
              "Pastel2" = "Pastel2",
              "Spectral" = "Spectral",
              "YlGnBu" = "YlGnBu",
              "RdYlBu" = "RdYlBu",
              "smplot2" = "smplot2"
            ),
            selected = settings$last_cluster_palette %||% "rainbow"
          )
          )
        )
      )
    )
  })

  outputOptions(output, "network_node_aesthetics", suspendWhenHidden = FALSE)

  output$param_node_aesthetics_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})


    tagList(
      fluidRow(
        column(6, selectInput("modify_params_node_network_shape", "Node Shape:",
                              choices = c("circle", "square", "rectangle", "oval", "triangle", "diamond"),
                              selected = settings$last_modify_params_node_network_shape %||% "circle")),
        column(6, numericInput("modify_params_node_network_size", "Node Size:", value = settings$last_modify_params_node_network_size %||% 15, min = 1))),
      conditionalPanel(
        condition = "input.modify_params_node_network_shape == 'rectangle' || input.modify_params_node_network_shape == 'oval' || input.modify_params_node_network_shape == 'diamond'",
        fluidRow(
          column(
            6,
            div(
              numericInput(
                "modify_params_node_network_width_height_ratio",
                label = HTML(paste(
                  icon("ruler-combined", style = "margin-right: 8px;"), "Width/Height Ratio"
                )),
                value = settings$last_modify_params_node_network_width_height_ratio %||% 1.6,
                min = 0.1,
                step = 0.1
              ),
              tags$span(
                icon("question-circle"),
                title = "Adjust the ratio of width to height for rectangle, oval, and diamond shapes.",
                style = "cursor: help; margin-left: 6px; color: #007bff;"
              ),
              style = "display: flex; align-items: center;"
            )
          ))
      ),
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_node_network_color", "Node Fill Color:", value = settings$last_modify_params_node_network_color %||% "#2F6391")),
        column(6, colourpicker::colourInput("modify_params_node_network_border_color", "Node Border Color:", value = settings$last_modify_params_node_network_border_color %||% "white"))),
      fluidRow(
        column(6, numericInput("modify_params_node_network_alpha", "Node Alpha:", value = settings$last_modify_params_node_network_alpha %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, numericInput("modify_params_node_network_border_width", "Node Border Width:", value = settings$last_modify_params_node_network_border_width %||% 1, min = 0.1, step = 0.1))
      )
    )
  })

  outputOptions(output, "param_node_aesthetics_network", suspendWhenHidden = FALSE)

  output$param_node_xy_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})


    tagList(
      fluidRow(
        column(
          6,
          numericInput(
            "network_node_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_node_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "network_node_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_node_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_node_xy_network", suspendWhenHidden = FALSE)


  output$network_edge_aesthetics <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})


    tagList(
      fluidRow(
        column(6, numericInput("line_width_net", "Edge Width:", value = settings$last_line_width %||% 1, step = 0.1)),
        column(6, colourpicker::colourInput("line_color_net", "Edge Color:", value = settings$last_line_color %||% "#000000"))
      ),
      fluidRow(
        column(6, numericInput("line_alpha_net", "Edge Alpha:", value = settings$last_line_alpha %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, numericInput("line_endpoint_spacing_net", "Line Endpoint Spacing:", value = settings$last_line_endpoint_spacing %||% 0, min = 0, step = 0.1))
      ),
      conditionalPanel(
        condition = "input.is_directed",
        fluidRow(
          column(6, selectInput("arrow_type_net", "Arrow Type:", choices = c("open", "closed"), selected = settings$last_arrow_type %||% "closed")),
          column(6, numericInput("arrow_size_net", "Arrow Size:", value = settings$last_arrow_size %||% 0.1, min = 0.1, step = 0.1))
        ))
    )
  })

  outputOptions(output, "network_edge_aesthetics", suspendWhenHidden = FALSE)

  output$scale_edge_width <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(6, numericInput("min_edge_width", "Min. Edge Width:", value = settings$last_min_edge_width %||% 0.5, min = 0.1, step = 0.1)),
        column(6, numericInput("max_edge_width", "Max. Edge Width:", value = settings$last_max_edge_width %||% 3, min = 0.1, step = 0.1))
      )
    )
  })

  outputOptions(output, "scale_edge_width", suspendWhenHidden = FALSE)

  output$bezier_network_edges <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(
          6,
          tags$span(
            icon("question-circle"),
            title = "Control the curvature magnitude of edges",
            style = "cursor: help; margin-left: 6px; color: #007bff;"
          ),
          sliderInput(
            "network_edges_curvature_magnitude",
            "Curvature Size:",
            min = 0,
            max = 2,
            value = settings$last_network_edges_curvature_magnitude %||% 0.3,
            step = 0.01
          )
        ),
        column(
          6,
          tags$span(
            icon("question-circle"),
            title = "Rotate the orientation of the edges by 180 degrees.",
            style = "cursor: help; margin-left: 6px; color: #007bff;"
          ),
          checkboxInput(
            "network_edges_rotate_curvature",
            "Rotate Curvature 180°",
            value = settings$last_network_edges_rotate_curvature %||% FALSE
          )
        )),
      fluidRow(
        column(
          6,
          tags$span(
            icon("question-circle"),
            title = "Control the asymmetry magnitude of edges.).",
            style = "cursor: help; margin-left: 6px; color: #007bff;"
          ),
          sliderInput(
            "network_edges_curvature_asymmetry",
            "Curvature Asymmetry:",
            min = -1,
            max = 1,
            value = settings$last_network_edges_curvature_asymmetry %||% 0,
            step = 0.1
          ),
        ),
      )
    )
  })

  outputOptions(output, "bezier_network_edges", suspendWhenHidden = FALSE)

  output$param_edge_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_edge_network_color", "Edge Color:", value = settings$last_modify_params_edge_network_color %||% "#cc3d3d")),
        column(6, selectInput("modify_params_edge_network_color_type", "Line Color Type:", choices = c("Single", "Gradient"),
                              selected = settings$last_modify_params_edge_network_color_type %||% "Single")),
        column(6, numericInput("modify_params_edge_network_linewidth",
                               "Linewidth:",
                               value = settings$last_modify_params_edge_network_linewidth %||% 1.5, min = 0.1, step = 0.1)),
        column(6, numericInput("modify_params_edge_network_alpha", "Edge Alpha:",
                               value = settings$last_modify_params_edge_network_alpha %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, selectInput("modify_params_edge_network_line_style", "Edge Line Style:",
                              choices = c("solid", "dashed", "dotted"),
                              selected = settings$last_modify_params_edge_network_line_style %||% "solid"
        )),
        conditionalPanel(
          condition = "input.modify_params_edge_network_color_type == 'Gradient'",
          fluidRow(
            column(6, colourpicker::colourInput("modify_params_edge_network_end_color", "Edge End Color:",
                                                value = settings$last_modify_params_edge_network_end_color %||% "#1262b3")),
            column(6, sliderInput("modify_params_edge_network_gradient_position", "Gradient Intersection:", min = 0.01, max = 0.99,
                                  value = settings$last_modify_params_edge_network_gradient_position %||% 0.5, step = 0.01),
                   tags$span(
                     icon("question-circle"),
                     title = "The close to 0, the more gradient favors the end color.",
                     style = "cursor: help; margin-left: 6px; color: #007bff;"
                   ),
                   style = "display: flex; align-items: center;"
            )
          )
        )
      )
    )
  })

  outputOptions(output, "param_edge_network", suspendWhenHidden = FALSE)

  output$param_bezier_network_edges <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(
          6,
          tags$span(
            icon("question-circle"),
            title = "Control the curvature magnitude of edges.).",
            style = "cursor: help; margin-left: 6px; color: #007bff;"
          ),
          sliderInput(
            "modify_params_network_edges_curvature_magnitude",
            "Curvature Size:",
            min = 0,
            max = 2,
            value = settings$last_modify_params_network_edges_curvature_magnitude %||% 0.3,
            step = 0.01
          ),
        ),
        column(
          6,
          tags$span(
            icon("question-circle"),
            title = "Rotate the orientation of the edges by 180 degrees.",
            style = "cursor: help; margin-left: 6px; color: #007bff;"
          ),
          checkboxInput(
            "modify_params_network_edges_rotate_curvature",
            "Rotate Curvature 180°",
            value = settings$last_modify_params_network_edges_rotate_curvature %||% FALSE
          )
        )),
      fluidRow(
        column(
          6,
          tags$span(
            icon("question-circle"),
            title = "Control the asymmetry of edges.).",
            style = "cursor: help; margin-left: 6px; color: #007bff;"
          ),
          sliderInput(
            "modify_params_network_edges_curvature_asymmetry",
            "Curvature Asymmetry:",
            min = -1,
            max = 1,
            value = settings$last_modify_params_network_edges_curvature_asymmetry %||% 0,
            step = 0.1
          ),
        )),
      fluidRow(
        column(
          6,
          numericInput(
            "modify_param_network_edge_x_shift",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_param_network_edge_x_shift %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "modify_param_network_edge_y_shift",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_param_network_edge_y_shift %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_bezier_network_edges", suspendWhenHidden = FALSE)


  output$param_edge_xy_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_edge_shift_network",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Values to 0")
               )
        )
      ),  # Dynamic dropdown
      fluidRow(
        column(
          6,
          numericInput(
            "modify_params_edge_network_start_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Coordinate X (Start)")),
            value = settings$last_modify_params_edge_network_start_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "modify_params_edge_network_start_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Coordinate Y (Start)")),
            value = settings$last_modify_params_edge_network_start_shift_y %||% 0,
            step = 0.1
          )
        )
      ),
      fluidRow(
        column(
          6,
          numericInput(
            "modify_params_edge_network_end_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Coordinate X (End)")),
            value = settings$last_modify_params_edge_network_end_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "modify_params_edge_network_end_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Coordinate Y (End)")),
            value = settings$last_modify_params_edge_network_end_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )

  })

  outputOptions(output, "param_edge_xy_network", suspendWhenHidden = FALSE)

  output$node_labels_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("node_label_color", "Text Color:", value = settings$last_node_label_color %||% "#FFFFFF")),
        column(6, numericInput("node_label_size", "Text Size:", value = settings$last_node_label_size %||% 15, min = 1, step = 1))
      ),
      fluidRow(
        column(6, selectInput("node_label_font", "Text Font:", choices = c("sans", "serif", "mono"), selected = settings$last_node_label_font %||% "sans")),
        column(6, numericInput("node_label_alpha", "Text Alpha:", value = settings$last_node_label_alpha %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, selectInput("node_label_fontface", "Fontface:", choices = c("plain", "bold", "italic"), selected = settings$last_node_label_fontface %||% "plain"))
      )
    )
  })

  outputOptions(output, "node_labels_network", suspendWhenHidden = FALSE)

  output$edge_labels_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(6, selectInput("edge_label_font", "Text Font:", choices = c("sans", "serif", "mono"), selected = settings$last_edge_label_font %||% "sans")),
        column(6, numericInput("edge_label_size", "Text Size:", value = settings$last_edge_label_size %||% 15, min = 1, step = 1))
      ),
      fluidRow(
        column(6, colourpicker::colourInput("edge_label_color", "Text Color:", value = settings$last_edge_label_color %||% "#000000")),
      ),
      fluidRow(
        column(6, checkboxInput("apply_edge_label_fill", "Fill Color for Texts", value = settings$last_apply_edge_label_fill %||% TRUE)),
        conditionalPanel(
          condition = "input.apply_edge_label_fill",
          column(6, colourpicker::colourInput("edge_label_fill", "Filling Color:", value = settings$last_edge_label_fill %||% "#FFFFFF"))
        )
      ),
      fluidRow(
        column(6, numericInput("edge_label_alpha", "Text Alpha:", value = settings$last_edge_label_alpha %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, selectInput("edge_label_fontface", "Fontface:", choices = c("plain", "bold", "italic"), selected = settings$last_edge_label_fontface %||% "plain"))
      ),
      fluidRow(
        column(6, checkboxInput("remove_edgelabels_net", "Remove Edge Labels", value = settings$last_remove_edgelabels_net %||% FALSE))
      )
    )
  })

  outputOptions(output, "edge_labels_network", suspendWhenHidden = FALSE)

  output$param_nodelabel_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_nodelabel_network_color", "Text Color:",
                                            value = settings$last_modify_params_nodelabel_network_color %||% "#FFFFFF")),
        column(6, numericInput("modify_params_nodelabel_network_size",
                               "Text Size:",
                               value = settings$last_modify_params_nodelabel_network_size %||% 18, min = 5, step = 1)),
        column(6, numericInput("modify_params_nodelabel_network_alpha", "Text Alpha:",
                               value = settings$last_modify_params_nodelabel_network_alpha %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, numericInput("modify_params_nodelabel_network_angle", "Angle (deg):",
                               value = settings$last_modify_params_nodelabel_network_angle %||% 0, min = -180, max = 180)),
        column(6, selectInput("modify_params_nodelabel_network_font", "Font:", choices = c("sans", "mono", "serif"),
                              selected = settings$last_modify_params_nodelabel_network_font %||% "sans")),
        column(6, selectInput("modify_params_nodelabel_network_fontface", "Font Face:", choices = c("plain", "bold", "italic"),
                              selected = settings$last_modify_params_nodelabel_network_fontface %||% "plain")
        )
      )
    )
  })

  outputOptions(output, "param_nodelabel_network", suspendWhenHidden = FALSE)

  output$param_nodelabel_xy_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_nodelabel_shift_network",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Shifts to 0")
               )
        )),
      fluidRow(
        column(
          6,
          numericInput(
            "modify_params_nodelabel_network_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_nodelabel_network_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "modify_params_nodelabel_network_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_nodelabel_network_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_nodelabel_xy_network", suspendWhenHidden = FALSE)

  output$param_edgelabel_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("modify_params_edgelabel_network_color", "Text Color:",
                                            value = settings$last_modify_params_edgelabel_network_color %||% "#cc3d3d"))
      ),
      fluidRow(
        column(6, checkboxInput("apply_modify_params_edgelabel_network_fill", "Fill Color for Texts", value = settings$last_apply_modify_params_edgelabel_network_fill %||% TRUE)),
        conditionalPanel(
          condition = "input.apply_modify_params_edgelabel_network_fill",
          column(6, colourpicker::colourInput("modify_params_edgelabel_network_fill", "Text Fill:",
                                              value = settings$last_modify_params_edgelabel_network_fill %||% "#FFFFFF"))
        )
      ),
      fluidRow(
        column(6, numericInput("modify_params_edgelabel_network_size",
                               "Text Size:",
                               value = settings$last_modify_params_edgelabel_network_size %||% 18, min = 5, step = 1)),
        column(6, numericInput("modify_params_edgelabel_network_alpha", "Text Alpha:",
                               value = settings$last_modify_params_edgelabel_network_alpha %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, numericInput("modify_params_edgelabel_network_angle", "Angle (deg):",
                               value = settings$last_modify_params_edgelabel_network_angle %||% 0, min = -180, max = 180)),
        column(6, selectInput("modify_params_edgelabel_network_font", "Font:", choices = c("sans", "mono", "serif"),
                              selected = settings$last_modify_params_edgelabel_network_font %||% "sans")),
        column(6, selectInput("modify_params_edgelabel_network_fontface", "Font Face:", choices = c("plain", "bold", "italic"),
                              selected = settings$last_modify_params_edgelabel_network_fontface %||% "plain")
        )
      )
    )
  })

  outputOptions(output, "param_edgelabel_network", suspendWhenHidden = FALSE)


  output$param_edgelabel_xy_network <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    tagList(
      fluidRow(
        column(6,
               actionButton(
                 "reset_edgelabel_xy_shift_network",
                 class = "redo-button01", label = tagList(icon("undo"), "Reset XY Shifts to 0")
               )
        )),
      fluidRow(
        column(
          6,
          numericInput(
            "modify_params_edgelabel_network_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_modify_params_edgelabel_network_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "modify_params_edgelabel_network_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_modify_params_edgelabel_network_shift_y %||% 0,
            step = 0.1
          )
        )
      )
    )
  })

  outputOptions(output, "param_edgelabel_xy_network", suspendWhenHidden = FALSE)


  output$group_aesthetics_point <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$group_settings[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("point_color_group", "Point Color:",
                                            value = settings$last_point_color_group %||% "#000000")),
        column(6, div(class = "custom-select", selectInput("shape_group",
                                                           HTML(paste(
                                                             icon("shapes", style = "margin-right: 6px;"), # icon for shapes
                                                             "Select Shape"
                                                           )),
                                                           choices = c("circle", "square", "rectangle", "oval", "triangle", "diamond"),
                                                           selected = settings$last_shape_group %||% "circle"
        )))
      ),
      fluidRow(
        column(6, numericInput("point_size_group", "Point Size:", value = settings$last_point_size_group %||% 15, min = 1)),
        column(6, numericInput("border_width_group", "Border Width:", value = settings$last_border_width_group %||% 1, min = 0))
      ),
      fluidRow(
        column(6, colourpicker::colourInput("border_color_group", "Border Color:", value = settings$last_border_color_group %||% "white")),
        column(6, numericInput("point_alpha_group", "Point Alpha:", value = settings$last_point_alpha_group %||% 1,
                               min = 0, max = 1, step = 0.1)) # Alpha input for points
      ),
      fluidRow(
        column(
          6,
          numericInput(
            "point_orientation_group",
            label = HTML(paste(
              icon("sync-alt", style = "margin-right: 8px;"), "Orientation (Degrees):"
            )),
            value = settings$last_point_orientation_group %||% 0,
            min = 0,
            max = 360,
            step = 1
          )
        ),
        conditionalPanel(
          condition = "input.shape_group == 'rectangle' || input.shape_group == 'oval' || input.shape_group == 'diamond'",
          column(
            6,
            div(
              numericInput(
                "width_height_ratio_group",
                label = HTML(paste(
                  icon("ruler-combined", style = "margin-right: 8px;"), "Width/Height Ratio"
                )),
                value = settings$last_width_height_ratio_group %||% 1.6,
                min = 0.1,
                step = 0.1
              ),
              tags$span(
                icon("question-circle"),
                title = "Adjust the ratio of width to height for rectangle, oval, and diamond shapes.",
                style = "cursor: help; margin-left: 6px; color: #007bff;"
              ),
              style = "display: flex; align-items: center;"
            )
          )
        )
      )
    )
  })

  outputOptions(output, "group_aesthetics_point", suspendWhenHidden = FALSE)


  output$group_aesthetics_line <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$group_settings[[group_id]]})

    tagList(
      fluidRow(
        column(6, colourpicker::colourInput("line_color_group", "Start Color:",
                                            value = settings$last_color_group %||% "#000000")),
        column(6, selectInput("color_type_group", "Line Color Type:", choices = c("Single", "Gradient"),
                              selected = settings$last_color_type_group %||% "Single"))
      ),
      conditionalPanel(
        condition = "input.color_type_group == 'Gradient'",
        fluidRow(
          column(6, colourpicker::colourInput("end_color_group", "End Color:",
                                              value = settings$last_end_color_group %||% "#D64542")),
          column(6, sliderInput("gradient_position_group", "Gradient Intersection:", min = 0.01, max = 0.99,
                                value = settings$last_gradient_position_group %||% 0.5, step = 0.01),
                 tags$span(
                   icon("question-circle"),
                   title = "The close to 0, the more gradient favors the end color.",
                   style = "cursor: help; margin-left: 6px; color: #007bff;"
                 ),
                 style = "display: flex; align-items: center;"
          )
        )
      ),
      fluidRow(
        column(6, numericInput("line_width_group", "Line Width:", value = settings$last_line_width_group %||% 1, min = 1)),
        column(6, numericInput("line_alpha_group", "Line Alpha:", value = settings$last_line_alpha_group %||% 1, min = 0,
                               max = 1, step = 0.1))
      ),
      fluidRow(
        column(6, selectInput("line_type_group", "Line Type:",
                              choices = c("Straight Line", "Straight Arrow", "Curved Line", "Curved Arrow"),
                              selected = settings$last_line_type_group %||% "Straight Line")),
        conditionalPanel(
          condition = "input.color_type_group == 'Single'",
          column(6, selectInput("line_style_group", "Line Style:", choices = c("solid", "dashed", "dotted"),
                                selected = settings$last_line_type_group %||% "solid"))
        )
      ),

      # Conditional display for curved lines
      conditionalPanel(
        condition = "input.line_type_group == 'Curved Line' || input.line_type_group == 'Curved Arrow'",
        fluidRow(
          column(6, sliderInput("curvature_magnitude_group", "Curvature Magnitude:", min = 0, max = 2,
                                value = settings$last_curavture_magnitude_group %||% 0.3, step = 0.01)),
          column(6, checkboxInput("rotate_curvature_group", "Rotate Curvature 180°",
                                  value = settings$last_rotate_curvature_group %||% FALSE))
        ),
        fluidRow(
          column(6, sliderInput("curvature_asymmetry_group", "Curvature Asymmetry", min = -1, max = 1,
                                value = settings$last_curvature_asymmetry_group %||% 0, step = 0.1))
        )
      ),

      # Conditional display for arrows
      conditionalPanel(
        condition = "input.line_type_group == 'Straight Arrow' || input.line_type_group == 'Curved Arrow'",
        fluidRow(
          column(6, selectInput("arrow_type_group", "Arrow Type:", choices = c("open", "closed"),
                                selected = settings$last_arrow_type_group %||% "open")),
          column(6, numericInput("arrow_size_group", "Arrow Size:",
                                 value = settings$last_arrow_size_group %||% 0.2, min = 0.1, step = 0.1))
        ),
        fluidRow(
          column(6, checkboxInput("two_way_arrow_group", "Two-way Arrow",
                                  value = settings$last_two_way_arrow_group %||% FALSE)) # two-way arrows checkbox
        )
      )
    )
  })

  outputOptions(output, "group_aesthetics_line", suspendWhenHidden = FALSE)

  output$group_aesthetics_annotation <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$group_settings[[group_id]]})

    tagList(
      fluidRow(
        column(6, selectInput("text_font_group", "Font:",
                              choices = c("sans", "serif", "mono"),
                              selected = settings$last_text_font_group %||% "sans"
        )),
        column(6, numericInput("text_size_group", "Text Size:",
                               value = settings$last_text_size_group %||% 20, min = 1))
      ),
      fluidRow(
        column(6, colourpicker::colourInput("text_color_group", "Color:",
                                            value = settings$last_text_color_group %||% "#000000")),
        column(6, numericInput("text_angle_group", "Angle (deg):",
                               value = settings$last_text_angle_group %||% 0))
      ),
      fluidRow(
        column(6, numericInput("text_alpha_group", "Text Alpha:",
                               value = settings$last_text_alpha_group %||% 1, min = 0, max = 1, step = 0.1)),
        column(6, selectInput("text_fontface_group", "Fontface:", choices = c("plain", "bold", "italic"),
                              selected = settings$last_text_fontface_group %||% "plain"))
      ),
      fluidRow(
        column(6, checkboxInput("apply_text_fill_group", "Fill Color for Texts", value = settings$last_apply_text_fill_group) %||% TRUE),
        conditionalPanel(
          condition = "input.apply_text_fill_group",
          column(6, colourpicker::colourInput("text_fill_group", "Filling Color:", value = settings$last_text_fill_group %||% NA))
        )
      )
    )

  })

  outputOptions(output, "group_aesthetics_annotation", suspendWhenHidden = FALSE)

  output$group_aesthetics_loop <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$group_settings[[group_id]]})

    tagList(
      fluidRow(
        column(6, numericInput("radius_group", "Radius:", value = settings$last_radius_group %||% 5, min = 0.1)),
        column(6, numericInput("line_width_loop_group", "Line Width:",
                               value = settings$last_line_width_loop_group %||% 1, min = 0.1))
      ),
      fluidRow(
        column(6, colourpicker::colourInput("line_color_loop_group", "Line Color:",
                                            value = settings$last_line_color_loop_group %||% "#000000")),
        column(6, numericInput("line_alpha_loop_group", "Line Alpha:",
                               value = settings$last_line_alpha_loop_group %||% 1, min = 0, max = 1, step = 0.1))
      ),
      fluidRow(
        column(6, selectInput("arrow_type_loop_group", "Arrow Type:", choices = c("open", "closed"),
                              selected = settings$last_arrow_type_loop_group %||% "open")),
        column(6, numericInput("arrow_size_loop_group", "Arrow Size:",
                               value = settings$last_arrow_size_loop_group %||% 0.2, min = 0.1, step = 0.1))
      ),
      fluidRow(
        column(6, numericInput("width_loop_group", "Loop Width:",
                               value = settings$last_width_loop_group %||% 1, min = 0.1)),
        column(6, numericInput("height_loop_group", "Loop Height:",
                               value = settings$last_height_loop_group %||% 1, min = 0.1))
      ),
      fluidRow(
        column(6, numericInput("gap_size_loop_group", "Gap Size:",
                               value = settings$last_gap_size_loop_group %||% 0.2, min = 0, max = 1, step = 0.05)),
        column(6, numericInput("orientation_loop_group", "Orientation (deg):",
                               value = settings$last_orientation_loop_group %||% 0, min = -180, max = 180))
      ),
      fluidRow(
        column(12, checkboxInput("two_way_arrow_loop_group", "Two-way Arrow",
                                 value = settings$last_two_way_arrow_loop_group %||% FALSE)) # checkbox for two-way self-loop arrows
      )
    )
  })

  outputOptions(output, "group_aesthetics_loop", suspendWhenHidden = FALSE)

  output$group_shift_xy <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$group_settings[[group_id]]})

    tagList(
      fluidRow(
        column(
          6,
          numericInput(
            "group_shift_x",
            label = HTML(paste(icon("arrows-alt-h", style = "margin-right: 6px;"), "Shift X")),
            value = settings$last_group_shift_x %||% 0,
            step = 0.1
          )
        ),
        column(
          6,
          numericInput(
            "group_shift_y",
            label = HTML(paste(icon("arrows-alt-v", style = "margin-right: 6px;"), "Shift Y")),
            value = settings$last_group_shift_y %||% 0,
            step = 0.1
          )
        )
      ),
      fluidRow(
        column(
          6,
          actionButton(
            "reset_group_shift",
            class = "redo-button01", label = tagList(icon("undo"), "Reset XY Values to 0")
          )
        )
      )
    )
  })

  outputOptions(output, "group_shift_xy", suspendWhenHidden = FALSE)

  observe({
    all_groups <- unique(c(
      if (!is.null(values$points)) unique(values$points$group),
      if (!is.null(values$lines)) unique(values$lines$group),
      if (!is.null(values$annotations)) unique(values$annotations$group),
      if (!is.null(values$loops)) unique(values$loops$group)
    ))

    all_groups <- all_groups[!is.na(all_groups) & all_groups != "" & !is.null(all_groups)]

    updateSelectizeInput(session, "groups_to_align",
                         choices = all_groups,
                         server = FALSE)
  })

  observeEvent(input$align_groups_button, {
    tryCatch({
      save_state()
      if (!is.null(input$groups_to_align) && length(input$groups_to_align) > 1) {
        groups_to_align <- input$groups_to_align
        align_method <- input$align_groups_method

        group_bounds <- list()

        for (group_id in groups_to_align) {
          all_x <- numeric()
          all_y <- numeric()

          # Get points coordinates
          if (!is.null(values$points)) {
            group_points_idx <- which(values$points$group == group_id &
                                        !values$points$locked &
                                        !values$points$lavaan &
                                        !values$points$network)
            if (length(group_points_idx) > 0) {
              all_x <- c(all_x, values$points$x[group_points_idx])
              all_y <- c(all_y, values$points$y[group_points_idx])
            }
          }


          if (!is.null(values$lines)) {
            group_lines_idx <- which(values$lines$group == group_id &
                                       !values$lines$locked &
                                       !values$lines$lavaan &
                                       !values$lines$network)

            if (length(group_lines_idx) > 0) {
              all_x <- c(all_x, values$lines$x_start[group_lines_idx], values$lines$x_end[group_lines_idx])
              all_y <- c(all_y, values$lines$y_start[group_lines_idx], values$lines$y_end[group_lines_idx])
            }
          }

          if (!is.null(values$annotations)) {
            group_annotations_idx <- which(values$annotations$group == group_id &
                                             !values$annotations$locked &
                                             !values$annotations$lavaan &
                                             !values$annotations$network)
            if (length(group_annotations_idx) > 0) {
              all_x <- c(all_x, values$annotations$x[group_annotations_idx])
              all_y <- c(all_y, values$annotations$y[group_annotations_idx])
            }
          }

          if (!is.null(values$loops) && nrow(values$loops) > 0) {
            group_loops_idx <- which(values$loops$group == group_id & !values$loops$locked)
            if (length(group_loops_idx) > 0) {
              all_x <- c(all_x, values$loops$x_center[group_loops_idx])
              all_y <- c(all_y, values$loops$y_center[group_loops_idx])
            }
          }

          if (length(all_x) > 0 && length(all_y) > 0) {
            group_bounds[[group_id]] <- list(
              left = min(all_x),
              right = max(all_x),
              top = max(all_y),
              bottom = min(all_y),
              center_x = mean(all_x),
              center_y = mean(all_y),
              width = max(all_x) - min(all_x),
              height = max(all_y) - min(all_y)
            )
          }
        }

        if (length(group_bounds) > 1) {
          if (align_method == "horizontal_center") {
            target_y <- mean(sapply(group_bounds, function(b) b$center_y))
            align_groups_to_horizontal_center(group_bounds, target_y, groups_to_align)
          }
          else if (align_method == "vertical_center") {
            target_x <- mean(sapply(group_bounds, function(b) b$center_x))
            align_groups_to_vertical_center(group_bounds, target_x, groups_to_align)
          }
          else if (align_method == "left") {
            target_x <- min(sapply(group_bounds, function(b) b$left))
            align_groups_to_left_edge(group_bounds, target_x, groups_to_align)
          }
          else if (align_method == "right") {
            target_x <- max(sapply(group_bounds, function(b) b$right))
            align_groups_to_right_edge(group_bounds, target_x, groups_to_align)
          }
          else if (align_method == "top") {
            target_y <- max(sapply(group_bounds, function(b) b$top))
            align_groups_to_top_edge(group_bounds, target_y, groups_to_align)
          }
          else if (align_method == "bottom") {
            target_y <- min(sapply(group_bounds, function(b) b$bottom))
            align_groups_to_bottom_edge(group_bounds, target_y, groups_to_align)
          }

          showNotification(
            paste("Aligned", length(groups_to_align), "groups using", align_method, "method"),
            type = "message"
          )

          output$plot <- renderPlot({
            recreate_plot()
          })
        }
      } else {
        showNotification("Please select at least 2 groups to align", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error aligning groups:", e$message), type = "error")
    })
  })

  # Alignment helper functions
  align_groups_to_horizontal_center <- function(group_bounds, target_y, groups_to_align) {
    for (group_id in groups_to_align) {
      bounds <- group_bounds[[group_id]]
      if (!is.null(bounds)) {
        shift_y <- target_y - bounds$center_y
        apply_vertical_shift(group_id, shift_y)
      }
    }
  }

  align_groups_to_vertical_center <- function(group_bounds, target_x, groups_to_align) {
    for (group_id in groups_to_align) {
      bounds <- group_bounds[[group_id]]
      if (!is.null(bounds)) {
        shift_x <- target_x - bounds$center_x
        apply_horizontal_shift(group_id, shift_x)
      }
    }
  }

  align_groups_to_left_edge <- function(group_bounds, target_x, groups_to_align) {
    for (group_id in groups_to_align) {
      bounds <- group_bounds[[group_id]]
      if (!is.null(bounds)) {
        shift_x <- target_x - bounds$left
        apply_horizontal_shift(group_id, shift_x)
      }
    }
  }

  align_groups_to_right_edge <- function(group_bounds, target_x, groups_to_align) {
    for (group_id in groups_to_align) {
      bounds <- group_bounds[[group_id]]
      if (!is.null(bounds)) {
        shift_x <- target_x - bounds$right
        apply_horizontal_shift(group_id, shift_x)
      }
    }
  }

  align_groups_to_top_edge <- function(group_bounds, target_y, groups_to_align) {
    for (group_id in groups_to_align) {
      bounds <- group_bounds[[group_id]]
      if (!is.null(bounds)) {
        shift_y <- target_y - bounds$top
        apply_vertical_shift(group_id, shift_y)
      }
    }
  }

  align_groups_to_bottom_edge <- function(group_bounds, target_y, groups_to_align) {
    for (group_id in groups_to_align) {
      bounds <- group_bounds[[group_id]]
      if (!is.null(bounds)) {
        shift_y <- target_y - bounds$bottom
        apply_vertical_shift(group_id, shift_y)
      }
    }
  }

  apply_horizontal_shift <- function(group_id, shift_x) {
    if (!is.null(values$points)) {
      group_points_idx <- which(values$points$group == group_id &
                                  !values$points$locked &
                                  !values$points$lavaan &
                                  !values$points$network)
      if (length(group_points_idx) > 0) {
        values$points$x[group_points_idx] <- values$points$x[group_points_idx] + shift_x
      }
    }

    if (!is.null(values$lines)) {
      group_lines_idx <- which(values$lines$group == group_id &
                                 !values$lines$locked &
                                 !values$lines$lavaan &
                                 !values$lines$network)
      if (length(group_lines_idx) > 0) {
        values$lines$x_start[group_lines_idx] <- values$lines$x_start[group_lines_idx] + shift_x
        values$lines$x_end[group_lines_idx] <- values$lines$x_end[group_lines_idx] + shift_x

        curved_lines_idx <- which(group_lines_idx &
                                    values$lines$type %in% c("Curved Line", "Curved Arrow"))
        if (length(curved_lines_idx) > 0) {
          control_points <- mapply(
            calculate_control_point,
            x_start = values$lines$x_start[curved_lines_idx],
            y_start = values$lines$y_start[curved_lines_idx],
            x_end = values$lines$x_end[curved_lines_idx],
            y_end = values$lines$y_end[curved_lines_idx],
            curvature_magnitude = values$lines$curvature_magnitude[curved_lines_idx],
            rotate_curvature = values$lines$rotate_curvature[curved_lines_idx],
            curvature_asymmetry = values$lines$curvature_asymmetry[curved_lines_idx],
            center_x = mean(values$points[group_points_idx]$x),
            center_y = mean(values$points[group_points_idx]$y),
            SIMPLIFY = FALSE
          )
          values$lines$ctrl_x[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_x"), 5)
          values$lines$ctrl_y[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_y"), 5)
          values$lines$ctrl_x2[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_x2"), 5)
          values$lines$ctrl_y2[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_y2"), 5)
        }
      }
    }

    if (!is.null(values$annotations)) {
      group_annotations_idx <- which(values$annotations$group == group_id &
                                       !values$annotations$locked &
                                       !values$annotations$lavaan &
                                       !values$annotations$network)
      if (length(group_annotations_idx) > 0) {
        values$annotations$x[group_annotations_idx] <- values$annotations$x[group_annotations_idx] + shift_x
      }
    }

    if (!is.null(values$loops) && nrow(values$loops) > 0) {
      group_loops_idx <- which(values$loops$group == group_id & !values$loops$locked)
      if (length(group_loops_idx) > 0) {
        values$loops$x_center[group_loops_idx] <- values$loops$x_center[group_loops_idx] + shift_x
      }
    }
  }

  apply_vertical_shift <- function(group_id, shift_y) {
    if (!is.null(values$points)) {
      group_points_idx <- which(values$points$group == group_id &
                                  !values$points$locked &
                                  !values$points$lavaan &
                                  !values$points$network)
      if (length(group_points_idx) > 0) {
        values$points$y[group_points_idx] <- values$points$y[group_points_idx] + shift_y
      }
    }

    if (!is.null(values$lines)) {
      group_lines_idx <- which(values$lines$group == group_id &
                                 !values$lines$locked &
                                 !values$lines$lavaan &
                                 !values$lines$network)
      if (length(group_lines_idx) > 0) {
        values$lines$y_start[group_lines_idx] <- values$lines$y_start[group_lines_idx] + shift_y
        values$lines$y_end[group_lines_idx] <- values$lines$y_end[group_lines_idx] + shift_y

        curved_lines_idx <- which(group_lines_idx &
                                    values$lines$type %in% c("Curved Line", "Curved Arrow"))
        if (length(curved_lines_idx) > 0) {
          control_points <- mapply(
            calculate_control_point,
            x_start = values$lines$x_start[curved_lines_idx],
            y_start = values$lines$y_start[curved_lines_idx],
            x_end = values$lines$x_end[curved_lines_idx],
            y_end = values$lines$y_end[curved_lines_idx],
            curvature_magnitude = values$lines$curvature_magnitude[curved_lines_idx],
            rotate_curvature = values$lines$rotate_curvature[curved_lines_idx],
            curvature_asymmetry = values$lines$curvature_asymmetry[curved_lines_idx],
            center_x = mean(values$points[group_points_idx]$x),
            center_y = mean(values$points[group_points_idx]$y),
            SIMPLIFY = FALSE
          )
          values$lines$ctrl_x[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_x"), 5)
          values$lines$ctrl_y[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_y"), 5)
          values$lines$ctrl_x2[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_x2"), 5)
          values$lines$ctrl_y2[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_y2"), 5)
        }
      }
    }

    if (!is.null(values$annotations)) {
      group_annotations_idx <- which(values$annotations$group == group_id &
                                       !values$annotations$locked &
                                       !values$annotations$lavaan &
                                       !values$annotations$network)
      if (length(group_annotations_idx) > 0) {
        values$annotations$y[group_annotations_idx] <- values$annotations$y[group_annotations_idx] + shift_y
      }
    }

    if (!is.null(values$loops) && nrow(values$loops) > 0) {
      group_loops_idx <- which(values$loops$group == group_id & !values$loops$locked)
      if (length(group_loops_idx) > 0) {
        values$loops$y_center[group_loops_idx] <- values$loops$y_center[group_loops_idx] + shift_y
      }
    }
  }


  calculate_group_boundaries <- function() {
    boundaries <- list()
    all_groups <- unique(c(
      if (!is.null(values$points)) unique(values$points$group),
      if (!is.null(values$lines)) unique(values$lines$group),
      if (!is.null(values$annotations)) unique(values$annotations$group),
      if (!is.null(values$loops)) unique(values$loops$group)
    ))

    all_groups <- all_groups[!is.na(all_groups) & all_groups != ""]

    for (group_id in all_groups) {
      all_x <- numeric()
      all_y <- numeric()

      if (!is.null(values$points)) {
        group_points <- which(values$points$group == group_id)
        if (length(group_points) > 0) {
          all_x <- c(all_x, values$points$x[group_points])
          all_y <- c(all_y, values$points$y[group_points])
        }
      }

      if (!is.null(values$lines)) {
        group_lines <- which(values$lines$group == group_id)
        if (length(group_lines) > 0) {
          all_x <- c(all_x, values$lines$x_start[group_lines], values$lines$x_end[group_lines])
          all_y <- c(all_y, values$lines$y_start[group_lines], values$lines$y_end[group_lines])
        }
      }

      if (!is.null(values$annotations)) {
        group_annotations <- which(values$annotations$group == group_id &
                                     values$annotations$group_label != TRUE)
        if (length(group_annotations) > 0) {
          all_x <- c(all_x, values$annotations$x[group_annotations])
          all_y <- c(all_y, values$annotations$y[group_annotations])
        }
      }

      if (!is.null(values$loops) && nrow(values$loops) > 0) {
        group_loops <- which(values$loops$group == group_id)
        if (length(group_loops) > 0) {
          all_x <- c(all_x, values$loops$x_center[group_loops])
          all_y <- c(all_y, values$loops$y_center[group_loops])
        }
      }

      if (length(all_x) > 0 && length(all_y) > 0) {
        boundaries[[group_id]] <- list(
          left = min(all_x),
          right = max(all_x),
          top = max(all_y),
          bottom = min(all_y),
          center_x = mean(all_x),
          center_y = mean(all_y),
          width = max(all_x) - min(all_x),
          height = max(all_y) - min(all_y)
        )
      }
    }

    return(boundaries)
  }

  calculate_label_position <- function(boundaries, position, offset, alignment = "relative",
                                       fixed_x = NULL, fixed_y = NULL, group_id = NULL) {

    base_pos <- list()

    if (position == "top") {
      base_pos$x <- boundaries$center_x
      base_pos$y <- boundaries$top + offset
    } else if (position == "bottom") {
      base_pos$x <- boundaries$center_x
      base_pos$y <- boundaries$bottom - offset
    } else if (position == "left") {
      base_pos$x <- boundaries$left - offset
      base_pos$y <- boundaries$center_y
    } else if (position == "right") {
      base_pos$x <- boundaries$right + offset
      base_pos$y <- boundaries$center_y
    } else if (position == "top_left") {
      base_pos$x <- boundaries$left - offset
      base_pos$y <- boundaries$top + offset
    } else if (position == "top_right") {
      base_pos$x <- boundaries$right + offset
      base_pos$y <- boundaries$top + offset
    } else if (position == "bottom_left") {
      base_pos$x <- boundaries$left - offset
      base_pos$y <- boundaries$bottom - offset
    } else if (position == "bottom_right") {
      base_pos$x <- boundaries$right + offset
      base_pos$y <- boundaries$bottom - offset
    }

    if (alignment == "fixed_x") {
      if (!is.null(fixed_x)) {
        base_pos$x <- fixed_x
      }
    } else if (alignment == "fixed_y") {
      if (!is.null(fixed_y)) {
        base_pos$y <- fixed_y
      }
    } else if (alignment == "fixed_grid") {
      if (!is.null(fixed_x)) base_pos$x <- fixed_x
      if (!is.null(fixed_y)) base_pos$y <- fixed_y
    }

    return(list(x = base_pos$x, y = base_pos$y))
  }

  observeEvent(input$update_group_labels, {
    tryCatch({
      save_state()

      # Remove existing group labels
      if (!is.null(values$annotations) && nrow(values$annotations) > 0) {
        values$annotations <- values$annotations[values$annotations$group_label != TRUE, ]
      }

      # Add new group labels if enabled
      if (input$show_group_labels) {
        boundaries <- calculate_group_boundaries()

        for (group_id in names(boundaries)) {
          # Calculate position with alignment options
          label_pos <- calculate_label_position(
            boundaries[[group_id]],
            input$group_label_position,
            input$group_label_offset,
            alignment = input$group_label_alignment,
            fixed_x = if (!is.null(input$group_label_fixed_x)) input$group_label_fixed_x else NULL,
            fixed_y = if (!is.null(input$group_label_fixed_y)) input$group_label_fixed_y else NULL,
            group_id = group_id
          )

          # Create new annotation for group label
          new_annotation <- data.frame(
            text = group_id,
            x = label_pos$x,
            y = label_pos$y,
            font = input$group_label_font,
            size = input$group_label_size,
            color = input$group_label_color,
            fill = NA,
            angle = input$group_label_angle,
            alpha = 1,
            fontface = input$group_label_fontface,
            math_expression = FALSE,
            hjust = 0.5,
            vjust = 0.5,
            lavaan = FALSE,
            network = FALSE,
            locked = FALSE,
            group_label = TRUE,
            loop_label = FALSE,
            group = group_id,
            stringsAsFactors = FALSE
          )

          # Add to annotations
          if (nrow(values$annotations) == 0) {
            values$annotations <- new_annotation
          } else {
            values$annotations <- rbind(values$annotations, new_annotation)
          }
        }

        # Determine alignment method for notification
        alignment_method <- switch(input$group_label_alignment,
                                   "relative" = "relative to each group's boundaries",
                                   "fixed_x" = "with fixed X coordinate alignment",
                                   "fixed_y" = "with fixed Y coordinate alignment",
                                   "fixed_grid" = "on a fixed grid")

        showNotification(
          paste("Group labels added for", length(boundaries), "groups", alignment_method),
          type = "message",
          duration = 5
        )

      } else {
        showNotification("Group labels removed", type = "message", duration = 5)
      }

      # Refresh plot
      output$plot <- renderPlot({
        recreate_plot()
      })

    }, error = function(e) {
      showNotification(
        paste("Error updating group labels:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  output$is_blavaan <- reactive({
    group_id <- as.character(input$group_select)
    bundleModelObject <- isolate({values$group_storage$sem[[group_id]]$bundleModelObject})

    inherits(bundleModelObject, "blavaan")
  })
  outputOptions(output, "is_blavaan", suspendWhenHidden = FALSE)

  is_blavaan <- reactive({
    if (is.null(input$group_select)) {
      return(FALSE)
    }

    group_id <- as.character(input$group_select)

    if (is.null(values$group_storage$sem[[group_id]])) {
      return(FALSE)
    }

    if (is.null(values$group_storage$sem[[group_id]]$bundleModelObject)) {
      return(FALSE)
    }

    inherits(values$group_storage$sem[[group_id]]$bundleModelObject, "blavaan")
  })


  output$is_qgraph <- reactive({
    group_id <- as.character(input$group_select)
    bundleObject <- isolate({values$group_storage$network[[group_id]]$bundleObject})
    bundleModelObject <- isolate({values$group_storage$network[[group_id]]$bundleModelObject})

    inherits(bundleObject, "qgraph") && !inherits(bundleModelObject, "lavaan")
  })
  outputOptions(output, "is_qgraph", suspendWhenHidden = FALSE)

  output$is_lavaan_model_obj <- reactive({
    group_id <- as.character(input$group_select)

    bundleObject <- isolate({values$group_storage$sem[[group_id]]$bundleObject})
    bundleModelObject <- isolate({values$group_storage$sem[[group_id]]$bundleModelObject})

    inherits(bundleObject, "qgraph") && inherits(bundleModelObject, "lavaan")
  })

  outputOptions(output, "is_lavaan_model_obj", suspendWhenHidden = FALSE)

  output$is_lavaan_any <- reactive({
    group_id <- as.character(input$group_select)

    bundleObject <- isolate({values$group_storage$sem[[group_id]]$bundleObject})
    bundleModelObject <- isolate({values$group_storage$sem[[group_id]]$bundleModelObject})

    ((is(bundleObject)[[1]] == "lavaan") && is.null(bundleModelObject)) ||
      (is(bundleModelObject)[[1]] == "lavaan") || (is_there_data_no_bundle$file == TRUE)
  })

  outputOptions(output, "is_lavaan_any", suspendWhenHidden = FALSE)

  output$is_sempath_or_grViz <- reactive({
    group_id <- as.character(input$group_select)

    bundleObject <- isolate({values$group_storage$sem[[group_id]]$bundleObject})
    bundleModelObject <- isolate({values$group_storage$sem[[group_id]]$bundleModelObject})

    (inherits(bundleObject, "qgraph") && is.null(bundleModelObject)) ||
      (inherits(bundleObject, "grViz") && inherits(bundleModelObject, "lavaan"))
  })

  outputOptions(output, "is_sempath_or_grViz", suspendWhenHidden = FALSE)

  output$is_sempath_or_tidysem_or_grViz <- reactive({
    group_id <- as.character(input$group_select)

    bundleObject <- isolate({values$group_storage$sem[[group_id]]$bundleObject})
    bundleModelObject <- isolate({values$group_storage$sem[[group_id]]$bundleModelObject})

    (inherits(bundleObject, "qgraph") && is.null(bundleModelObject)) ||
      (inherits(bundleObject, "sem_graph") && inherits(bundleModelObject, "lavaan")) ||
      (inherits(bundleObject, "grViz") && inherits(bundleModelObject, "lavaan"))
  })

  outputOptions(output, "is_sempath_or_tidysem_or_grViz", suspendWhenHidden = FALSE)

  output$is_sempath <- reactive({
    group_id <- as.character(input$group_select)

    bundleObject <- isolate({values$group_storage$sem[[group_id]]$bundleObject})
    bundleModelObject <- isolate({values$group_storage$sem[[group_id]]$bundleModelObject})

    (inherits(bundleObject, "qgraph") && is.null(bundleModelObject))
  })

  outputOptions(output, "is_sempath", suspendWhenHidden = FALSE)

  output$is_bipartite_or_ggnet2_or_qgraph <- reactive({
    group_id <- as.character(input$group_select)

    bundleObject <- isolate({values$group_storage$network[[group_id]]$bundleObject})
    bundleModelObject <- isolate({values$group_storage$network[[group_id]]$bundleModelObject})

    bipartite_check <- if (inherits(bundleObject, "igraph")) {
      !is.null(bundleObject) && igraph::is_bipartite(bundleObject)
    } else if (inherits(bundleObject, "network")) {
      !is.null(bundleObject) && network::is.bipartite(bundleObject)
    } else {
      FALSE
    }
    ggnet2_check <- inherits(bundleObject, "ggplot") && inherits(bundleModelObject, "network")
    qgraph_check <- inherits(bundleObject, "qgraph") && !inherits(bundleModelObject, "lavaan")

    bipartite_check || ggnet2_check || qgraph_check
  })
  outputOptions(output, "is_bipartite_or_ggnet2_or_qgraph", suspendWhenHidden = FALSE)

  output$is_lavaan_bundle_obj <- reactive({
    group_id <- as.character(input$group_select)

    bundleObject <- isolate({values$group_storage$sem[[group_id]]$bundleObject})
    bundleModelObject <- isolate({values$group_storage$sem[[group_id]]$bundleModelObject})

    inherits(bundleObject, "lavaan") && (is.null(bundleModelObject))
  })
  outputOptions(output, "is_lavaan_bundle_obj", suspendWhenHidden = FALSE)

  output$is_grViz <- reactive({
    group_id <- as.character(input$group_select)
    bundleObject <- isolate({values$group_storage$sem[[group_id]]$bundleObject})
    bundleModelObject <- isolate({values$group_storage$sem[[group_id]]$bundleModelObject})
    inherits(bundleObject, "grViz") && inherits(bundleModelObject, "lavaan")
  })
  outputOptions(output, "is_grViz", suspendWhenHidden = FALSE)

  curve_ctrl_values <- reactiveValues(
    ctrl_x = numeric(),
    ctrl_y = numeric(),
    ctrl_x2 = numeric(),
    ctrl_y2 = numeric()
  )

  sig_diff_results <- reactiveVal(NULL)

  output$is_bipartite <- reactive({
    group_id <- as.character(input$group_select)

    bundleObject <- isolate({values$group_storage$network[[group_id]]$bundleObject})

    if (inherits(bundleObject, "igraph")) {
      if (is.null(bundleObject)) {
        FALSE
      } else {
        igraph::is_bipartite(bundleObject)
      }
    } else if (inherits(bundleObject, "network")) {
      if (is.null(bundleObject)) {
        FALSE
      } else {
        network::is.bipartite(bundleObject)
      }

    } else {
      FALSE
    }
  })
  outputOptions(output, "is_bipartite", suspendWhenHidden = FALSE)

  uploaded_data <- reactiveVal(NULL)

  # last_hover <- reactiveVal(NULL)
  # debounced_hover <- debounce(reactive(input$plot_hover), 100)
  #
  # output$hover_info <- renderText({
  #   hover <- debounced_hover()
  #   if (!is.null(hover$x) && !is.null(hover$y)) {
  #     coords <- paste0("Hovered at: X = ", round(hover$x, 2), ", Y = ", round(hover$y, 2))
  #     last_hover(coords)
  #   } else {
  #     last_hover("Hover coordinates not available")
  #   }
  #   last_hover() %||% "Hover over the plot to see normalized X and Y coordinates"
  # })


  observeEvent(input$add_fit_stats_to_canvas, {
    tryCatch({
      req(input$group_select)

      group_id <- as.character(input$group_select)
      model_fit <- values$group_storage$sem[[group_id]]$current

      if (is.null(model_fit)) {
        showNotification("No model available for selected group.", type = "warning")
        return()
      }

      save_state()

      # Check if any statistics are selected
      has_stats <- FALSE
      if (!is_blavaan()) {
        if (input$chisq || input$cfi_tli || input$rmsea || input$srmr) {
          has_stats <- TRUE
        }
      } else {
        if (input$ppp || input$dic || input$waic || input$looic) {
          has_stats <- TRUE
        }
      }

      if (!has_stats) {
        showNotification("No fit statistics selected.", type = "warning")
        return()
      }

      # Determine fill color
      fill_color <- if (input$apply_fill_text_stats && !is.null(input$text_stats_fill)) {
        input$text_stats_fill
      } else {
        NA
      }

      # Generate the annotations data frame with exact same logic as renderPrint
      new_text <- generate_fit_stats_annotations(
        model_fit = model_fit,
        chisq = input$chisq,
        cfi_tli = input$cfi_tli,
        rmsea = input$rmsea,
        srmr = input$srmr,
        ppp = input$ppp,
        dic = input$dic,
        waic = input$waic,
        looic = input$looic,
        text_stats_size = input$text_stats_size,
        text_stats_color = input$text_stats_color,
        text_stats_fill = fill_color,
        text_stats_font = input$text_stats_font,
        text_stats_fontface = input$text_stats_fontface,
        text_stats_alpha = input$text_stats_alpha,
        text_stats_angle = input$text_stats_angle,
        which_group = group_id,
        x_stats_location = input$x_stats_location,
        y_stats_location = input$y_stats_location,
        text_stats_hjust = as.numeric(input$text_stats_hjust),
        text_stats_vjust = as.numeric(input$text_stats_vjust),
        is_blavaan = is_blavaan()
      )

      if (!is.null(new_text)) {
        values$annotations <- rbind(values$annotations, new_text)
        showNotification("Fit statistics added to canvas successfully.", type = "message")
      } else {
        showNotification("Failed to generate fit statistics annotations.", type = "warning")
      }

    }, error = function(e) {
      showNotification(
        paste("Error adding fit statistics to canvas:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$add_fit_stats_to_canvas_network, {
    tryCatch({
      req(input$group_select)

      group_id <- as.character(input$group_select)
      network_obj <- values$group_storage$network[[group_id]]$bundleObject

      if (is.null(network_obj)) {
        showNotification("No network object available for selected group.", type = "warning")
        return()
      }

      # Check if it's actually a network object
      if (!(inherits(network_obj, "qgraph") || inherits(network_obj, "igraph"))) {
        showNotification("Selected group is not a network object.", type = "warning")
        return()
      }

      # Save current state for undo
      save_state()

      # Check if any network statistics are selected
      has_network_stats <- FALSE
      if (input$net_modularity || input$net_clustering ||
          input$net_density || input$net_avg_path) {
        has_network_stats <- TRUE
      }

      if (!has_network_stats) {
        showNotification("No network statistics selected.", type = "warning")
        return()
      }

      # Determine fill color
      fill_color <- if (input$apply_fill_text_stats_network &&
                        !is.null(input$text_stats_fill_network)) {
        input$text_stats_fill_network
      } else {
        NA
      }

      # Generate the annotations data frame
      new_text <- generate_network_stats_annotations(
        network_obj = network_obj,
        use_clustering = input$use_clustering,
        clustering_method = if(input$use_clustering) input$clustering_method else NULL,
        net_modularity = input$net_modularity,
        net_clustering = input$net_clustering,
        net_density = input$net_density,
        net_avg_path = input$net_avg_path,
        text_stats_size = input$text_stats_size_network,
        text_stats_color = input$text_stats_color_network,
        text_stats_fill = fill_color,
        text_stats_font = input$text_stats_font_network,
        text_stats_fontface = input$text_stats_fontface_network,
        text_stats_alpha = input$text_stats_alpha_network,
        text_stats_angle = input$text_stats_angle_network,
        which_group = group_id,
        x_stats_location = input$x_stats_location_network,
        y_stats_location = input$y_stats_location_network,
        text_stats_hjust = as.numeric(input$text_stats_hjust_network),
        text_stats_vjust = as.numeric(input$text_stats_vjust_network)
      )

      if (!is.null(new_text)) {
        # Add to annotations
        values$annotations <- rbind(values$annotations, new_text)

        # Store network stats in group storage for later use
        # values$group_storage$network[[group_id]]$network_stats <- list(
        #   calculated = calculate_network_stats(
        #     network_obj = network_obj,
        #     use_clustering = input$use_clustering,
        #     clustering_method = if(input$use_clustering) input$clustering_method else NULL
        #   ),
        #   display_settings = list(
        #     show_modularity = input$net_modularity,
        #     show_clustering = input$net_clustering,
        #     show_density = input$net_density,
        #     show_avg_path = input$net_avg_path,
        #     use_clustering = input$use_clustering,
        #     clustering_method = if(input$use_clustering) input$clustering_method else NULL
        #   )
        # )

        showNotification("Network statistics added to canvas successfully.", type = "message")
      } else {
        showNotification("Failed to generate network statistics annotations.", type = "warning")
      }

    }, error = function(e) {
      showNotification(
        paste("Error adding network statistics to canvas:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  output$fitStatsOutput <- renderPrint({
    req(input$group_select, input$show_fit_stats)

    group_id <- as.character(input$group_select)

    # model_fit <- values$group_storage$sem[[group_id]]$bundleModelObject
    # if (is.null(model_fit)) {
    model_fit <- isolate({values$group_storage$sem[[group_id]]$current})
    # }

    tryCatch({
      # Create vector of requested measures based on checkbox inputs
      measures <- c()
      if (input$chisq) measures <- c(measures, "chisq", "df", "pvalue")
      if (input$cfi_tli) measures <- c(measures, "cfi", "tli")
      if (input$rmsea) measures <- c(measures, "rmsea", "rmsea.ci.lower", "rmsea.ci.upper")
      if (input$srmr) measures <- c(measures, "srmr")

      if (input$ppp) measures <- c(measures, "ppp")
      if (input$dic) measures <- c(measures, "dic")
      if (input$waic) measures <- c(measures, "waic")
      if (input$looic) measures <- c(measures, "looic")

      if (length(measures) > 0) {
        # Get fit measures with error handling
        stats <- tryCatch(
          fitMeasures(model_fit, measures),
          error = function(e) {
            showNotification(
              "Failed to calculate fit statistics. The model may not be converged or specified correctly.",
              type = "error",
              duration = 5
            )
            return(NULL)
          }
        )

        if (is.null(stats)) {
          cat("Fit statistics unavailable - check model convergence")
          return()
        }

        cat("Model Fit Statistics:\n\n")


        if (is_blavaan()) {
          if (input$ppp) {
            cat(sprintf("Posterior Predictive P-value (PPP) = %.3f\n", stats["ppp"]))
          }
          if (input$dic) {
            cat(sprintf("DIC = %.3f\n", stats["dic"]))
          }
          if (input$waic) {
            cat(sprintf("WAIC = %.3f\n", stats["waic"]))
          }
          if (input$looic) {
            cat(sprintf("LOOIC = %.3f\n", stats["looic"]))
          }
        } else {
          if (input$chisq) {
            cat(sprintf("χ²(%d) = %.2f, p = %.3f\n",
                        stats["df"], stats["chisq"], stats["pvalue"]))
          }
          if (input$cfi_tli) {
            cat(sprintf("CFI = %.3f\nTLI = %.3f\n", stats["cfi"], stats["tli"]))
          }
          if (input$rmsea) {
            cat(sprintf("RMSEA = %.3f [%.3f, %.3f]\n",
                        stats["rmsea"], stats["rmsea.ci.lower"], stats["rmsea.ci.upper"]))
          }
          if (input$srmr) {
            cat(sprintf("SRMR = %.3f\n", stats["srmr"]))
          }
        }


      } else {
        cat("No fit statistics selected")
      }
    },
    error = function(e) {
      showNotification(
        paste("Unexpected error while displaying fit statistics:", e$message),
        type = "error",
        duration = 5
      )
      cat("Error occurred while generating fit statistics")
    })
  })

  output$networkStatsOutput <- renderPrint({
    req(input$group_select, input$show_fit_stats_network)

    group_id <- as.character(input$group_select)
    network_obj <- isolate({values$group_storage$network[[group_id]]$bundleObject})

    if (!(inherits(network_obj, "qgraph") || inherits(network_obj, "igraph"))) {
      cat("Not a network object")
      return()
    }

    tryCatch({
      stats <- calculate_network_stats(
        network_obj = network_obj,
        use_clustering = input$use_clustering,
        clustering_method = if(input$use_clustering) input$clustering_method else NULL
      )

      if (is.null(stats)) {
        cat("Network statistics unavailable")
        return()
      }

      cat("Network Statistics Preview:\n")
      cat("===========================\n\n")

      if (input$net_modularity && !is.null(stats$modularity)) {
        if (input$use_clustering) {
          method_name <- switch(input$clustering_method,
                                "louvain" = "Louvain",
                                "leiden" = "Leiden",
                                "walktrap" = "Walktrap",
                                "fast_greedy" = "Fast Greedy",
                                input$clustering_method
          )
          cat(sprintf("Modularity (%s) = %.3f\n", method_name, stats$modularity))
        } else {
          cat("Modularity: Clustering not enabled (enable in Network Layout settings)\n")
        }
      }

      # Other stats preview
      if (input$net_clustering && !is.null(stats$clustering)) {
        cat(sprintf("Clustering Coefficient = %.3f\n", stats$clustering))
      }

      if (input$net_density && !is.null(stats$density)) {
        cat(sprintf("Density = %.3f\n", stats$density))
      }

      if (input$net_avg_path && !is.null(stats$avg_path_length)) {
        cat(sprintf("Average Path Length = %.3f\n", stats$avg_path_length))
      }

      # Communities preview
      if (input$use_clustering && !is.null(stats$n_communities)) {
        cat(sprintf("Number of Communities = %d\n", stats$n_communities))
        if (!is.null(stats$community_sizes)) {
          cat("Community sizes:", paste(stats$community_sizes, collapse = ", "), "\n")
        }
      }

    }, error = function(e) {
      cat(sprintf("Error calculating network statistics: %s\n", e$message))
    })
  })

  output$param_select_ui <- renderUI({
    req(values$group_storage$sem[[as.character(input$group_select)]]$current)
    values$force_ui_refresh

    group_id <- as.character(input$group_select)
    current <- isolate({values$group_storage$sem[[group_id]]$current})

    params <- parameterEstimates(current)
    choices <- paste(params$lhs, params$op, params$rhs)

    selectInput(
      "param_select",
      "Parameter to Modify",
      choices = choices,
      selected = choices[1]  # Set initial selection
    )
  })


  observeEvent(input$param_select, {
    req(input$param_select, values$group_storage$sem[[as.character(input$group_select)]]$current)

    group_id <- as.character(input$group_select)
    settings <- values$group_storage$sem[[group_id]]

    params <- parameterEstimates(settings$current)

    if (length(unique(params$group)) > 1) {
      group_info <- lavInspect(settings$current, "group.label")
      group_level <- settings$last_group_level
      group_number <- which(group_info == group_level)
      params <- params[params$group == group_number, ]
    }

    selected_parts <- unlist(strsplit(input$param_select, " "))


    if (length(selected_parts) == 3) {
      lhs <- selected_parts[1]
      op <- selected_parts[2]
      rhs <- selected_parts[3]

      match_row <- params[params$lhs == lhs &
                            params$op == op &
                            params$rhs == rhs, ]

      if (nrow(match_row) > 0) {
        current_est <- round(match_row$est[1], 5)
        updateNumericInput(session, "param_value", value = current_est)
      }
    } else if (length(selected_parts) == 2) {
      lhs <- selected_parts[1]
      op <- selected_parts[2]

      match_row <- params[params$lhs == lhs &
                            params$op == op, ]

      if (nrow(match_row) > 0) {
        current_est <- round(match_row$est[1], 5)
        updateNumericInput(session, "param_value", value = current_est)
      }

    }
  }, ignoreNULL = TRUE)

  observeEvent(input$loop_angle_right, {
    updateNumericInput(session, "modify_params_loop_location_value", value = 0)
  })
  observeEvent(input$loop_angle_top_right, {
    updateNumericInput(session, "modify_params_loop_location_value", value = 45)
  })
  observeEvent(input$loop_angle_top, {
    updateNumericInput(session, "modify_params_loop_location_value", value = 90)
  })
  observeEvent(input$loop_angle_top_left, {
    updateNumericInput(session, "modify_params_loop_location_value", value = 135)
  })

  observeEvent(input$loop_angle_left, {
    updateNumericInput(session, "modify_params_loop_location_value", value = 180)
  })
  observeEvent(input$loop_angle_bottom_left, {
    updateNumericInput(session, "modify_params_loop_location_value", value = 225)
  })
  observeEvent(input$loop_angle_bottom, {
    updateNumericInput(session, "modify_params_loop_location_value", value = 270)
  })
  observeEvent(input$loop_angle_bottom_right, {
    updateNumericInput(session, "modify_params_loop_location_value", value = 315)
  })

  last_multi_group_first <- reactiveVal(NULL)

  observe({
    req(input$highlight_multi_group == TRUE)

    lavaan_points0 <- values$points[values$points$lavaan, ]
    lavaan_lines0 <- values$lines[values$lines$lavaan, ]
    lavaan_annotations0 <- values$annotations[values$annotations$lavaan, ]

    all_groups <- unique(c(lavaan_points0$group, lavaan_lines0$group, lavaan_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {
      group_select_value <- input$group_select

      if (is.null(group_select_value) || group_select_value == "" ||
          !group_select_value %in% all_groups) {
        group_select_value <- all_groups[1]
      }

      updateSelectInput(
        session,
        "multi_group_first",
        choices = all_groups,
        selected = group_select_value
      )

      shinyjs::disable("multi_group_first")
    }
  })

  observeEvent(input$multi_group_first, {
    if (!is.null(input$multi_group_first) && input$multi_group_first != "") {
      last_multi_group_first(input$multi_group_first)
    }
  })


  last_multi_group_second <- reactiveVal(NULL)

  observe({
    lavaan_points0 <- values$points[values$points$lavaan, ]
    lavaan_lines0 <- values$lines[values$lines$lavaan, ]
    lavaan_annotations0 <- values$annotations[values$annotations$lavaan, ]

    all_groups <- unique(c(lavaan_points0$group, lavaan_lines0$group, lavaan_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {
      current_first_group <- input$multi_group_first

      # For multi_group_second - exclude the current first group
      if (length(all_groups) == 1) {
        current_selection <- NULL
        available_groups <- character(0)  # No choices available
      } else {
        available_groups <- setdiff(all_groups, current_first_group)

        current_selection <- if (!is.null(last_multi_group_second()) && last_multi_group_second() %in% available_groups) {
          last_multi_group_second()
        } else if (length(available_groups) > 0) {
          available_groups[1]
        } else {
          NULL
        }
      }

      updateSelectInput(session, "multi_group_second", choices = available_groups, selected = current_selection)
    }
  })

  observeEvent(input$multi_group_second, {
    if (!is.null(input$multi_group_second) && input$multi_group_second != "") {
      last_multi_group_second(input$multi_group_second)
    }
  })


  last_multi_group_first_free <- reactiveVal(NULL)

  observe({
    req(input$highlight_free_path_multi_group == TRUE)

    lavaan_points0 <- values$points[values$points$lavaan, ]
    lavaan_lines0 <- values$lines[values$lines$lavaan, ]
    lavaan_annotations0 <- values$annotations[values$annotations$lavaan, ]

    all_groups <- unique(c(lavaan_points0$group, lavaan_lines0$group, lavaan_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {
      group_select_value <- input$group_select

      if (is.null(group_select_value) || group_select_value == "" ||
          !group_select_value %in% all_groups) {
        group_select_value <- all_groups[1]
      }

      updateSelectInput(
        session,
        "multi_group_first_free",
        choices = all_groups,
        selected = group_select_value
      )

      shinyjs::disable("multi_group_first_free")
    }
  })

  observeEvent(input$multi_group_first_free, {
    if (!is.null(input$multi_group_first_free) && input$multi_group_first_free != "") {
      last_multi_group_first_free(input$multi_group_first_free)
    }
  })


  last_multi_group_second_free <- reactiveVal(NULL)

  observe({
    lavaan_points0 <- values$points[values$points$lavaan, ]
    lavaan_lines0 <- values$lines[values$lines$lavaan, ]
    lavaan_annotations0 <- values$annotations[values$annotations$lavaan, ]

    all_groups <- unique(c(lavaan_points0$group, lavaan_lines0$group, lavaan_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {
      current_first_group <- input$multi_group_first_free

      # For multi_group_second - exclude the current first group
      if (length(all_groups) == 1) {
        current_selection <- NULL
        available_groups <- character(0)  # No choices available
      } else {
        available_groups <- setdiff(all_groups, current_first_group)

        current_selection <- if (!is.null(last_multi_group_second_free()) && last_multi_group_second_free() %in% available_groups) {
          last_multi_group_second_free()
        } else if (length(available_groups) > 0) {
          available_groups[1]
        } else {
          NULL
        }
      }

      updateSelectInput(session, "multi_group_second_free", choices = available_groups, selected = current_selection)
    }
  })

  observeEvent(input$multi_group_second_free, {
    if (!is.null(input$multi_group_second_free) && input$multi_group_second_free != "") {
      last_multi_group_second_free(input$multi_group_second_free)
    }
  })

  params_status_single <- reactiveVal(NULL)

  last_multi_group_first_combine <- reactiveVal(NULL)

  observe({
    lavaan_points0 <- values$points[values$points$lavaan, ]
    lavaan_lines0 <- values$lines[values$lines$lavaan, ]
    lavaan_annotations0 <- values$annotations[values$annotations$lavaan, ]

    all_groups <- unique(c(lavaan_points0$group, lavaan_lines0$group, lavaan_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {
      current_selection <- if (!is.null(last_multi_group_first_combine()) && last_multi_group_first_combine() %in% all_groups) {
        last_multi_group_first_combine()
      } else {
        all_groups[1]
      }

      updateSelectInput(session, "multi_group_first_combine", choices = all_groups, selected = current_selection)
    }
  })

  observeEvent(input$multi_group_first_combine, {
    if (!is.null(input$multi_group_first_combine) && input$multi_group_first_combine != "") {
      last_multi_group_first_combine(input$multi_group_first_combine)
    }
  })


  last_multi_group_second_combine <- reactiveVal(NULL)

  observe({
    lavaan_points0 <- values$points[values$points$lavaan, ]
    lavaan_lines0 <- values$lines[values$lines$lavaan, ]
    lavaan_annotations0 <- values$annotations[values$annotations$lavaan, ]

    all_groups <- unique(c(lavaan_points0$group, lavaan_lines0$group, lavaan_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {
      current_first_group <- input$multi_group_first_combine

      # exclude the current first group
      if (length(all_groups) == 1) {
        current_selection <- NULL
        available_groups <- character(0)  # No choices available
      } else {
        available_groups <- setdiff(all_groups, current_first_group)

        current_selection <- if (!is.null(last_multi_group_second_combine()) && last_multi_group_second_combine() %in% available_groups) {
          last_multi_group_second_combine()
        } else if (length(available_groups) > 0) {
          available_groups[1]
        } else {
          NULL
        }
      }

      updateSelectInput(session, "multi_group_second_combine", choices = available_groups, selected = current_selection)
    }
  })

  observeEvent(input$multi_group_second_combine, {
    if (!is.null(input$multi_group_second_combine) && input$multi_group_second_combine != "") {
      last_multi_group_second_combine(input$multi_group_second_combine)
    }
  })


  last_modify_sem_group <- reactiveVal(NULL)

  observe({
    req(input$multi_group_sem_layout == TRUE)

    lavaan_points0 <- values$points[values$points$lavaan, ]
    lavaan_lines0 <- values$lines[values$lines$lavaan, ]
    lavaan_annotations0 <- values$annotations[values$annotations$lavaan, ]

    all_groups <- unique(c(lavaan_points0$group, lavaan_lines0$group, lavaan_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {
      # Always use group_select as the value
      group_select_value <- input$group_select

      # Validate group_select is in available groups
      if (is.null(group_select_value) || group_select_value == "" ||
          !group_select_value %in% all_groups) {
        group_select_value <- all_groups[1]
      }

      # Update modify_sem_group_select to match group_select
      updateSelectInput(
        session,
        "modify_sem_group_select",
        choices = all_groups,
        selected = group_select_value
      )

      shinyjs::disable("modify_sem_group_select")
    }
  })

  observeEvent(input$modify_sem_group_select, {
    if (!is.null(input$modify_sem_group_select) && input$modify_sem_group_select != "") {
      last_modify_sem_group(input$modify_sem_group_select)
    }
  })


  last_match_sem_group <- reactiveVal(NULL)

  observe({
    lavaan_points0 <- values$points[values$points$lavaan, ]
    lavaan_lines0 <- values$lines[values$lines$lavaan, ]
    lavaan_annotations0 <- values$annotations[values$annotations$lavaan, ]

    all_groups <- unique(c(lavaan_points0$group, lavaan_lines0$group, lavaan_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {

      current_modify_group <- input$modify_sem_group_select

      if (length(all_groups) == 1) {
        current_match_selection <- NULL
        available_groups <- character(0)  # No choices available
      } else {
        available_groups <- setdiff(all_groups, current_modify_group)

        current_match_selection <- if (!is.null(last_match_sem_group()) && last_match_sem_group() %in% available_groups) {
          last_match_sem_group()
        } else if (length(available_groups) > 0) {
          available_groups[1]
        } else {
          NULL
        }
      }

      updateSelectInput(session, "modify_sem_group_match", choices = available_groups, selected = current_match_selection)
    }
  })

  observeEvent(input$modify_sem_group_match, {
    if (!is.null(input$modify_sem_group_match) && input$modify_sem_group_match != "") {
      last_match_sem_group(input$modify_sem_group_match)
    }
  })

  last_modify_network_group <- reactiveVal(NULL)

  observe({
    req(input$multi_group_network_layout == TRUE)

    network_points0 <- values$points[values$points$network, ]
    network_lines0 <- values$lines[values$lines$network, ]
    network_annotations0 <- values$annotations[values$annotations$network, ]

    all_groups <- unique(c(network_points0$group, network_lines0$group, network_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {
      # Always use group_select as the value
      group_select_value <- input$group_select

      # Validate group_select is in available groups
      if (is.null(group_select_value) || group_select_value == "" ||
          !group_select_value %in% all_groups) {
        group_select_value <- all_groups[1]
      }

      # Update modify_network_group_select to match group_select
      updateSelectInput(
        session,
        "modify_network_group_select",
        choices = all_groups,
        selected = group_select_value
      )

      shinyjs::disable("modify_network_group_select")
    }
  })

  observeEvent(input$modify_network_group_select, {
    if (!is.null(input$modify_network_group_select) && input$modify_network_group_select != "") {
      last_modify_network_group(input$modify_network_group_select)
    }
  })


  last_match_network_group <- reactiveVal(NULL)

  observe({
    network_points0 <- values$points[values$points$network, ]
    network_lines0 <- values$lines[values$lines$network, ]
    network_annotations0 <- values$annotations[values$annotations$network, ]

    all_groups <- unique(c(network_points0$group, network_lines0$group, network_annotations0$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {

      current_modify_group <- input$modify_network_group_select

      if (length(all_groups) == 1) {
        current_match_selection <- NULL
        available_groups <- character(0)  # No choices available
      } else {
        available_groups <- setdiff(all_groups, current_modify_group)

        current_match_selection <- if (!is.null(last_match_network_group()) && last_match_network_group() %in% available_groups) {
          last_match_network_group()
        } else if (length(available_groups) > 0) {
          available_groups[1]
        } else {
          NULL
        }
      }

      updateSelectInput(session, "modify_network_group_match", choices = available_groups, selected = current_match_selection)
    }
  })

  observeEvent(input$modify_network_group_match, {
    if (!is.null(input$modify_network_group_match) && input$modify_network_group_match != "") {
      last_match_network_group(input$modify_network_group_match)
    }
  })


  last_used_group <- reactiveVal(NULL)
  last_modify_group <- reactiveVal(NULL)
  last_position_group <- reactiveVal(NULL)
  last_delete_group <- reactiveVal(NULL)
  last_lock_group <- reactiveVal(NULL)

  all_groups <- reactive({
    groups <- unique(c(values$points$group, values$lines$group, values$annotations$group, values$loops$group))
    sort(groups[!is.na(groups) & groups != ""])
  })

  update_group_select <- function(input_id, last_used_val, groups, session) {
    if (length(groups) > 0) {
      current_last_val <- isolate(last_used_val())

      current_selection <- if (!is.null(current_last_val) && current_last_val %in% groups) {
        current_last_val
      } else {
        groups[1]
      }

      updateSelectInput(session, input_id, choices = groups, selected = current_selection)
    }
  }

  observeEvent(all_groups(), {
    current_groups <- all_groups()

    shinyjs::delay(50, {
      update_group_select("group_select", last_used_group, current_groups, session)
      update_group_select("modify_group_select", last_modify_group, current_groups, session)
      update_group_select("position_group_select", last_position_group, current_groups, session)
      update_group_select("delete_group_select", last_delete_group, current_groups, session)
      update_group_select("lock_group_select", last_lock_group, current_groups, session)
    })
  }, ignoreInit = TRUE)
  observeEvent(input$group_select, {
    new_val <- input$group_select
    current_val <- isolate(last_used_group())

    if (!is.null(new_val) && new_val != "" && !identical(new_val, current_val)) {
      last_used_group(new_val)
    }
  })

  observeEvent(input$modify_group_select, {
    new_val <- input$modify_group_select
    current_val <- isolate(last_modify_group())

    if (!is.null(new_val) && new_val != "" && !identical(new_val, current_val)) {
      last_modify_group(new_val)
    }
  })

  observeEvent(input$position_group_select, {
    new_val <- input$position_group_select
    current_val <- isolate(last_position_group())

    if (!is.null(new_val) && new_val != "" && !identical(new_val, current_val)) {
      last_position_group(new_val)
    }
  })

  observeEvent(input$delete_group_select, {
    new_val <- input$delete_group_select
    current_val <- isolate(last_delete_group())

    if (!is.null(new_val) && new_val != "" && !identical(new_val, current_val)) {
      last_delete_group(new_val)
    }
  })

  observeEvent(input$lock_group_select, {
    new_val <- input$lock_group_select
    current_val <- isolate(last_lock_group())

    if (!is.null(new_val) && new_val != "" && !identical(new_val, current_val)) {
      last_lock_group(new_val)
    }
  })


  observeEvent(input$modify_group, {
    tryCatch({
      selected_rows <- NULL
      table_type <- NULL

      if (!is.null(input$data_table_rows_selected)) {
        selected_rows <- input$data_table_rows_selected
        table_type <- "points"
      } else if (!is.null(input$line_table_rows_selected)) {
        selected_rows <- input$line_table_rows_selected
        table_type <- "lines"
      } else if (!is.null(input$annotation_table_rows_selected)) {
        selected_rows <- input$annotation_table_rows_selected
        table_type <- "annotations"
      } else if (!is.null(input$loop_table_rows_selected)) {
        selected_rows <- input$loop_table_rows_selected
        table_type <- "loops"
      }

      if (!is.null(selected_rows)) {
        save_state()

        modified_rows <- 0

        df <- switch(table_type,
                     "points" = values$points,
                     "lines" = values$lines,
                     "annotations" = values$annotations,
                     "loops" = values$loops)

        if (!is.null(df) && nrow(df) >= max(selected_rows)) {
          valid_rows <- selected_rows[selected_rows <= nrow(df)]

          if (length(valid_rows) > 0) {
            condition_met <- df$lavaan[valid_rows] == FALSE &
              df$network[valid_rows] == FALSE & df$locked[valid_rows] == FALSE

            rows_to_modify <- valid_rows[condition_met]

            if (length(rows_to_modify) > 0) {
              df$group[rows_to_modify] <- input$modify_group_select

              switch(table_type,
                     "points" = values$points <- df,
                     "lines" = values$lines <- df,
                     "annotations" = values$annotations <- df,
                     "loops" = values$loops <- df)

              modified_rows <- length(rows_to_modify)
            }
          }
        }

        if (modified_rows > 0) {
          showNotification(
            paste("Successfully modified", modified_rows, "row(s).",
                  "Only rows with locked='lavaan' and network=FALSE were updated."),
            type = "message"
          )
        } else if (length(selected_rows) > 0) {
          showNotification(
            paste("No modifications made. Selected rows must have lavaan=FALSE, network=FALSE and locked=FALSE."),
            type = "warning"
          )
        }

      } else {
        showNotification("No element selected. Please select an element (row) whose group assignment needs to be modified.",
                         type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error modifying group labels:", e$message), type = "error")
    })
  })

  observeEvent(input$new_group, {
    tryCatch({
      selected_rows <- NULL
      table_type <- NULL

      if (!is.null(input$data_table_rows_selected)) {
        selected_rows <- input$data_table_rows_selected
        table_type <- "points"
      } else if (!is.null(input$line_table_rows_selected)) {
        selected_rows <- input$line_table_rows_selected
        table_type <- "lines"
      } else if (!is.null(input$annotation_table_rows_selected)) {
        selected_rows <- input$annotation_table_rows_selected
        table_type <- "annotations"
      } else if (!is.null(input$loop_table_rows_selected)) {
        selected_rows <- input$loop_table_rows_selected
        table_type <- "loops"
      }

      if (!is.null(selected_rows)) {
        save_state()

        modified_rows <- 0

        df <- switch(table_type,
                     "points" = values$points,
                     "lines" = values$lines,
                     "annotations" = values$annotations,
                     "loops" = values$loops)

        if (!is.null(df) && nrow(df) >= max(selected_rows)) {
          valid_rows <- selected_rows[selected_rows <= nrow(df)]

          if (length(valid_rows) > 0) {
            condition_met <- df$lavaan[valid_rows] == FALSE &
              df$network[valid_rows] == FALSE &
              df$locked[valid_rows] == FALSE

            rows_to_modify <- valid_rows[condition_met]

            if (length(rows_to_modify) > 0) {
              df$group[rows_to_modify] <- input$new_group_select

              switch(table_type,
                     "points" = values$points <- df,
                     "lines" = values$lines <- df,
                     "annotations" = values$annotations <- df,
                     "loops" = values$loops <- df)

              modified_rows <- length(rows_to_modify)
            }
          }
        }

        if (modified_rows > 0) {
          showNotification(
            paste("Successfully added new group to", modified_rows, "row(s).",
                  "Only rows with lavaan=FALSE, network=FALSE, and locked=FALSE were updated."),
            type = "message"
          )
        } else if (length(selected_rows) > 0) {
          showNotification(
            paste("No group added. Selected rows must have lavaan=FALSE, network=FALSE, and locked=FALSE."),
            type = "warning"
          )
        }

      } else {
        showNotification("No element selected. Please select an element (row) whose group assignment needs to be added",
                         type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error adding group labels:", e$message), type = "error")
    })
  })

  observe({
    req(input$rename_group_labels == TRUE)


    all_groups <- unique(c(values$points$group, values$lines$group, values$annotations$group, values$loops$group))
    all_groups <- sort(all_groups[!is.na(all_groups) & all_groups != ""])

    if (length(all_groups) > 0) {
      # Always use group_select as the value
      group_select_value <- input$group_select

      # Validate group_select is in available groups
      if (is.null(group_select_value) || group_select_value == "" ||
          !group_select_value %in% all_groups) {
        group_select_value <- all_groups[1]
      }

      updateSelectInput(
        session,
        "rename_group_select",
        choices = all_groups,
        selected = group_select_value
      )
    }
  })

  observeEvent(input$rename_group, {
    tryCatch({
      group_to_rename <- input$rename_group_select
      new_group_name <- input$new_group_name

      if (!is.null(group_to_rename) && group_to_rename != "" &&
          !is.null(new_group_name) && new_group_name != "") {

        save_state()

        rename_count <- 0

        if (!is.null(values$points) && "group" %in% names(values$points)) {
          rows_to_rename <- which(values$points$group == group_to_rename)
          if (length(rows_to_rename) > 0) {
            values$points$group[rows_to_rename] <- new_group_name
            rename_count <- rename_count + length(rows_to_rename)
          }
        }

        if (!is.null(values$lines) && "group" %in% names(values$lines)) {
          rows_to_rename <- which(values$lines$group == group_to_rename)
          if (length(rows_to_rename) > 0) {
            values$lines$group[rows_to_rename] <- new_group_name
            rename_count <- rename_count + length(rows_to_rename)
          }
        }

        if (!is.null(values$annotations) && "group" %in% names(values$annotations)) {
          rows_to_rename <- which(values$annotations$group == group_to_rename)
          if (length(rows_to_rename) > 0) {
            values$annotations$group[rows_to_rename] <- new_group_name
            rename_count <- rename_count + length(rows_to_rename)
          }
        }

        if (!is.null(values$loops) && "group" %in% names(values$loops)) {
          rows_to_rename <- which(values$loops$group == group_to_rename)
          if (length(rows_to_rename) > 0) {
            values$loops$group[rows_to_rename] <- new_group_name
            rename_count <- rename_count + length(rows_to_rename)
          }
        }

        if (!is.null(values$group_storage$modifications) &&
            group_to_rename %in% names(values$group_storage$modifications)) {

          values$group_storage$modifications[[new_group_name]] <-
            values$group_storage$modifications[[group_to_rename]]

          values$group_storage$modifications[[group_to_rename]] <- NULL

          rename_count <- rename_count + 1  # Count as 1 group modified
        }

        if (!is.null(values$group_storage$modifications_network) &&
            group_to_rename %in% names(values$group_storage$modifications_network)) {

          values$group_storage$modifications_network[[new_group_name]] <-
            values$group_storage$modifications_network[[group_to_rename]]

          values$group_storage$modifications_network[[group_to_rename]] <- NULL

          rename_count <- rename_count + 1  # Count as 1 group modified
        }

        if (!is.null(values$group_storage$sem) &&
            group_to_rename %in% names(values$group_storage$sem)) {

          values$group_storage$sem[[new_group_name]] <-
            values$group_storage$sem[[group_to_rename]]
          values$group_storage$sem[[group_to_rename]] <- NULL
        }

        if (!is.null(values$group_storage$network) &&
            group_to_rename %in% names(values$group_storage$network)) {

          values$group_storage$network[[new_group_name]] <-
            values$group_storage$network[[group_to_rename]]
          values$group_storage$network[[group_to_rename]] <- NULL
        }

        if (rename_count > 0) {
          summary_msg <- paste(
            "Renamed group '", group_to_rename, "' to '", new_group_name,
            "' (", rename_count, " elements updated)", sep = ""
          )
          showNotification(summary_msg, type = "message")

        } else {
          showNotification(paste("No elements found in group '", group_to_rename, "'", sep = ""),
                           type = "warning")
        }

      } else {
        showNotification("Please select a group and enter a new name", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error renaming group:", e$message), type = "error")
    })
  })

  observeEvent(input$delete_group, {
    tryCatch({
      group_to_delete <- input$delete_group_select

      if (!is.null(group_to_delete) && group_to_delete != "") {
        save_state()

        deleted_counts <- list()

        if (!is.null(values$points) && "group" %in% names(values$points)) {
          rows_to_delete <- which(values$points$group == group_to_delete)
          if (length(rows_to_delete) > 0) {
            values$points <- values$points[-rows_to_delete, , drop = FALSE]
            deleted_counts$points <- length(rows_to_delete)
          }
        }

        if (!is.null(values$lines) && "group" %in% names(values$lines)) {
          rows_to_delete <- which(values$lines$group == group_to_delete)
          if (length(rows_to_delete) > 0) {
            values$lines <- values$lines[-rows_to_delete, , drop = FALSE]
            deleted_counts$lines <- length(rows_to_delete)
          }
        }

        if (!is.null(values$annotations) && "group" %in% names(values$annotations)) {
          rows_to_delete <- which(values$annotations$group == group_to_delete)
          if (length(rows_to_delete) > 0) {
            values$annotations <- values$annotations[-rows_to_delete, , drop = FALSE]
            deleted_counts$annotations <- length(rows_to_delete)
          }
        }

        if (!is.null(values$loops) && "group" %in% names(values$loops)) {
          rows_to_delete <- which(values$loops$group == group_to_delete)
          if (length(rows_to_delete) > 0) {
            values$loops <- values$loops[-rows_to_delete, , drop = FALSE]
            deleted_counts$loops <- length(rows_to_delete)
          }
        }

        if (length(deleted_counts) > 0) {
          summary_msg <- paste(
            "Deleted group", group_to_delete, ":",
            paste(
              sapply(names(deleted_counts), function(x) {
                paste(deleted_counts[[x]], x)
              }), collapse = ", "
            )
          )
          showNotification(summary_msg, type = "message")
        } else {
          showNotification(paste("No elements found in group", group_to_delete), type = "warning")
        }

      } else {
        showNotification("Please select a group to delete.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error deleting group:", e$message), type = "error")
    })
  })


  observeEvent(input$clear_group, {
    tryCatch({
      selected_rows <- NULL
      table_type <- NULL

      if (!is.null(input$data_table_rows_selected)) {
        selected_rows <- input$data_table_rows_selected
        table_type <- "points"
      } else if (!is.null(input$line_table_rows_selected)) {
        selected_rows <- input$line_table_rows_selected
        table_type <- "lines"
      } else if (!is.null(input$annotation_table_rows_selected)) {
        selected_rows <- input$annotation_table_rows_selected
        table_type <- "annotations"
      } else if (!is.null(input$loop_table_rows_selected)) {
        selected_rows <- input$loop_table_rows_selected
        table_type <- "loops"
      }

      if (!is.null(selected_rows)) {
        save_state()

        cleared_rows <- 0

        df <- switch(table_type,
                     "points" = values$points,
                     "lines" = values$lines,
                     "annotations" = values$annotations,
                     "loops" = values$loops)

        if (!is.null(df) && nrow(df) >= max(selected_rows)) {
          valid_rows <- selected_rows[selected_rows <= nrow(df)]

          if (length(valid_rows) > 0) {
            condition_met <- df$lavaan[valid_rows] == FALSE &
              df$network[valid_rows] == FALSE &
              df$locked[valid_rows] == FALSE

            rows_to_clear <- valid_rows[condition_met]

            if (length(rows_to_clear) > 0) {
              df$group[rows_to_clear] <- NA

              switch(table_type,
                     "points" = values$points <- df,
                     "lines" = values$lines <- df,
                     "annotations" = values$annotations <- df,
                     "loops" = values$loops <- df)

              cleared_rows <- length(rows_to_clear)
            }
          }
        }

        if (cleared_rows > 0) {
          showNotification(
            paste("Successfully cleared groups for", cleared_rows, "row(s).",
                  "Only rows with lavaan=FALSE, network=FALSE, and locked=FALSE were cleared."),
            type = "message"
          )
        } else if (length(selected_rows) > 0) {
          showNotification(
            paste("No groups cleared. Selected rows must have lavaan=FALSE, network=FALSE, and locked=FALSE."),
            type = "warning"
          )
        }

      } else {
        showNotification("No element selected. Please select an element (row) whose group label needs to be cleared",
                         type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error clearing group labels:", e$message), type = "error")
    })
  })


  observeEvent(input$lock_group, {
    tryCatch({
      group_to_lock <- input$lock_group_select

      if (!is.null(group_to_lock) && group_to_lock != "") {
        save_state()

        locked_counts <- list()

        rows_to_lock <- which(values$points$group == group_to_lock)
        if (length(rows_to_lock) > 0) {
          values$points$locked[rows_to_lock] <- TRUE
        }

        rows_to_lock <- which(values$lines$group == group_to_lock)
        if (length(rows_to_lock) > 0) {
          values$lines$locked[rows_to_lock] <- TRUE
        }

        rows_to_lock <- which(values$annotations$group == group_to_lock)
        if (length(rows_to_lock) > 0) {
          values$annotations$locked[rows_to_lock] <- TRUE
        }

        rows_to_lock <- which(values$loops$group == group_to_lock)
        if (length(rows_to_lock) > 0) {
          values$loops$locked[rows_to_lock] <- TRUE
        }

        showNotification(paste("Elements successfully locked in group", group_to_lock), type = "message")

      } else {
        showNotification("Please select a group to lock", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error locking group:", e$message), type = "error")
    })
  })

  output$param_edgelabel_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    edges <- isolate({values$group_storage$sem[[group_id]]$edges})

    title <- "Edge Label(s) to Modify"

    choices <- paste0(edges$source, ' to ', edges$target)
    selected <- isolate({values$group_storage$sem[[group_id]]$last_param_edgelabel_select}) %||% character(0)

    selectizeInput(
      "param_edgelabel_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge label(s)...'
      )
    )
  })


  output$param_edgelabel_text_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    edges <- isolate({values$group_storage$sem[[group_id]]$edges})

    title <- "Edge Label(s) to Modify"

    choices <- paste0(edges$source, ' to ', edges$target)
    selected <- isolate({values$group_storage$sem[[group_id]]$last_params_edgelabel_text}) %||% character(0)

    selectizeInput(
      "param_edgelabel_text_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge label(s)...'
      )
    )
  })


  output$param_edgelabel_xy_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    edges <- isolate({values$group_storage$sem[[group_id]]$edges})

    title <- "Edge Label(s) to Modify"

    choices <- paste0(edges$source, ' to ', edges$target)
    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_xy_select}) %||% character(0)

    selectizeInput(
      "param_edgelabel_xy_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge label(s)...'
      )
    )
  })


  output$param_edge_xy_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    edges <- isolate({values$group_storage$sem[[group_id]]$edges})

    title <- "Edge(s) Position to Modify"

    choices <- paste0(edges$source, ' to ', edges$target)
    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_params_edge_xy_select}) %||% character(0)

    selectizeInput(
      "param_edge_xy_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge(s)...'
      )
    )
  })


  output$param_edge_xy_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    edges <- isolate({values$group_storage$network[[group_id]]$edges})

    title <- "Edge(s) Position to Modify"

    choices <- paste0(edges$source, ' to ', edges$target)
    selected <- isolate({values$group_storage$last_param_edge_xy_network_select}) %||% character(0)

    selectizeInput(
      "param_edge_xy_network_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge(s)...'
      )
    )
  })

  output$param_nodelabel_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Node(s) to Modify"

    choices <- nodes$node
    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_select}) %||% character(0)

    if (length(choices) == 0) {
      choices <- "No available nodes"
    }

    selectizeInput(
      "param_nodelabel_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })

  output$param_nodelabel_xy_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Node(s) to Modify"

    choices <- nodes$node
    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_xy_select}) %||% character(0)

    if (length(choices) == 0) {
      choices <- "No available nodes"
    }

    selectizeInput(
      "param_nodelabel_xy_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })


  output$param_nodelabel_text_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Node(s) to Modify"

    choices <- nodes$node
    selected <- isolate({values$group_storage$sem[[group_id]]$last_params_nodelabel_text_select}) %||% character(0)

    if (length(choices) == 0) {
      choices <- "No available nodes"
    }

    selectizeInput(
      "param_nodelabel_text_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })


  output$param_node_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Node(s) to Modify"

    choices <- nodes$node
    selected <- isolate({values$group_storage$sem[[group_id]]$last_param_node_select})  %||% character(0)

    if (length(choices) == 0) {
      choices <- "No available nodes"
    }

    selectizeInput(
      "param_node_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })


  output$param_edge_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    edges <- isolate({values$group_storage$sem[[group_id]]}$edges)

    title <- "Edge(s) to Modify"

    choices <- paste0(edges$source, ' to ', edges$target)
    selected <- isolate({
      values$group_storage$sem[[group_id]]$last_param_edge_select
    }) %||% character(0)

    selectizeInput(
      "param_edge_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge(s)...'
      )
    )
  })


  output$param_cov_edge_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)

    edges <- isolate({
      values$group_storage$sem[[group_id]]$edges
    })

    title <- "Edge(s) to Modify"

    choices <- paste0(edges$source, ' to ', edges$target)
    selected <- isolate({
      values$group_storage$sem[[group_id]]$last_modify_which_cov_edge
    }) %||% character(0)

    selectizeInput(
      "param_cov_edge_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge(s)...'
      )
    )
  })

  output$param_node_xy_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Node(s) Position to Modify"

    choices <- nodes$node
    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_which_node_shift_xy}) %||% character(0)

    if (length(choices) == 0) {
      choices <- "No available nodes"
    }

    selectizeInput(
      "param_node_xy_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })


  output$param_latent_node_xy_select_ui <- renderUI({
    values$force_ui_refresh

    group_id <- as.character(input$group_select)
    settings <- values$group_storage$sem[[group_id]]

    latent_nodes <- extract_latent_from_fit(settings$current)

    if (is.null(settings$current)) {
      if (!is.null(settings$bundleObject) && inherits(settings$bundleObject, "qgraph")) {
        node_names <- names(settings$bundleObject$graphAttributes$Nodes$labels)
        if (is.null(node_names)) node_names <- settings$bundleObject$graphAttributes$Nodes$labels
        node_types <- settings$bundleObject$graphAttributes$Nodes$shape
        latent_nodes <- node_names[node_types == "circle"]
      }
    }

    choices <- latent_nodes

    title <- "Latent Group Position to Modify"

    if (length(choices) == 0) {
      choices <- "No available latent nodes"
    }

    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_which_node_shift_xy}) %||% choices[1]

    selectInput(
      "param_latent_node_xy_select",
      title,
      choices = choices,
      selected = selected
    )
  })

  output$param_latent_node_angle_select_ui <- renderUI({
    values$force_ui_refresh

    group_id <- as.character(input$group_select)
    settings <- values$group_storage$sem[[group_id]]

    latent_nodes <- extract_latent_from_fit(settings$current)

    if (is.null(settings$current)) {
      if (!is.null(settings$bundleObject) && inherits(settings$bundleObject, "qgraph")) {
        node_names <- names(settings$bundleObject$graphAttributes$Nodes$labels)
        if (is.null(node_names)) node_names <- settings$bundleObject$graphAttributes$Nodes$labels
        node_types <- settings$bundleObject$graphAttributes$Nodes$shape
        latent_nodes <- node_names[node_types == "circle"]
      }
    }

    choices <- latent_nodes

    title <- "Latent Group Position to Modify"

    if (length(choices) == 0) {
      choices <- "No available latent nodes"
    }

    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_which_node_shift_angle}) %||% choices[1]

    selectInput(
      "param_latent_node_angle_select",
      title,
      choices = choices,
      selected = selected
    )
  })

  output$param_loop_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Loop Arrow(s) to Modify"

    choices <- nodes$node
    choices <- choices[!grepl("^Intercept_", choices)]

    last_residuals <- values$group_storage$sem[[group_id]]$last_residuals

    loop_names_remove <- values$group_storage$sem[[group_id]]$last_loop_names_remove_hi
    if (!is.null(loop_names_remove) && length(loop_names_remove) > 0 &&
        !all(loop_names_remove == "")) {
      choices <- choices[!choices %in% loop_names_remove]
    } else {
      last_residuals_logical <- as.logical(last_residuals %||% FALSE)
      if (last_residuals_logical == FALSE) {
        choices <- NULL
      }
    }

    selected <- isolate({values$group_storage$sem[[group_id]]$last_param_loop_select}) %||% character(0)

    if (is.null(choices) || length(choices) == 0) {
      choices <- "No available loop arrows"
    }

    selectizeInput(
      "param_loop_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select loop arrow(s)...'
      )
    )
  })


  output$param_looplabel_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Loop Arrow(s) to Modify"

    choices <- nodes$node
    choices <- choices[!grepl("^Intercept_", choices)]

    last_residuals <- values$group_storage$sem[[group_id]]$last_residuals

    loop_names_remove <- values$group_storage$sem[[group_id]]$last_loop_names_remove_hi
    if (!is.null(loop_names_remove) && length(loop_names_remove) > 0 &&
        !all(loop_names_remove == "")) {
      choices <- choices[!choices %in% loop_names_remove]
    } else {
      last_residuals_logical <- as.logical(last_residuals %||% FALSE)
      if (last_residuals_logical == FALSE) {
        choices <- NULL
      }
    }

    selected <- isolate({values$group_storage$sem[[group_id]]$last_param_looplabel_select}) %||% character(0)

    if (is.null(choices) || length(choices) == 0) {
      choices <- "No available loop arrows"
    }

    selectizeInput(
      "param_looplabel_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select loop arrow(s)...'
      )
    )
  })


  output$param_loop_xy_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Loop Arrow(s) to Modify (Position)"

    choices <- nodes$node
    choices <- choices[!grepl("^Intercept_", choices)]

    last_residuals <- values$group_storage$sem[[group_id]]$last_residuals

    loop_names_remove <- values$group_storage$sem[[group_id]]$last_loop_names_remove_hi
    if (!is.null(loop_names_remove) && length(loop_names_remove) > 0 &&
        !all(loop_names_remove == "")) {
      choices <- choices[!choices %in% loop_names_remove]
    } else {
      last_residuals_logical <- as.logical(last_residuals %||% FALSE)
      if (last_residuals_logical == FALSE) {
        choices <- NULL
      }
    }

    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_which_loop_shift_xy}) %||% character(0)

    if (is.null(choices) || length(choices) == 0) {
      choices <- "No available loop arrows"
    }

    selectizeInput(
      "param_loop_xy_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select loop arrow(s)...'
      )
    )
  })


  output$param_loop_location_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Loop Arrow(s) to Modify:"

    choices <- nodes$node
    choices <- choices[!grepl("^Intercept_", choices)]

    last_residuals <- values$group_storage$sem[[group_id]]$last_residuals

    loop_names_remove <- values$group_storage$sem[[group_id]]$last_loop_names_remove_hi
    if (!is.null(loop_names_remove) && length(loop_names_remove) > 0 &&
        !all(loop_names_remove == "")) {
      choices <- choices[!choices %in% loop_names_remove]
    } else {
      last_residuals_logical <- as.logical(last_residuals %||% FALSE)
      if (last_residuals_logical == FALSE) {
        choices <- NULL
      }
    }

    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_which_loop_location}) %||% character(0)

    if (is.null(choices) || length(choices) == 0) {
      choices <- "No available loop arrows"
    }

    selectizeInput(
      "param_loop_location_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select loop arrow(s)...'
      )
    )
  })


  output$param_looplabel_xy_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Loop Arrow(s) to Modify (Label Position)"

    choices <- nodes$node
    choices <- choices[!grepl("^Intercept_", choices)]

    last_residuals <- values$group_storage$sem[[group_id]]$last_residuals

    loop_names_remove <- values$group_storage$sem[[group_id]]$last_loop_names_remove_hi
    if (!is.null(loop_names_remove) && length(loop_names_remove) > 0 &&
        !all(loop_names_remove == "")) {
      choices <- choices[!choices %in% loop_names_remove]
    } else {
      last_residuals_logical <- as.logical(last_residuals %||% FALSE)
      if (last_residuals_logical == FALSE) {
        choices <- NULL
      }
    }

    selected <- isolate({values$group_storage$sem[[group_id]]$last_modify_which_looplabel_shift_xy}) %||% character(0)

    if (is.null(choices) || length(choices) == 0) {
      choices <- "No available loop arrows"
    }

    selectizeInput(
      "param_looplabel_xy_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select loop arrow(s)...'
      )
    )
  })


  output$param_looplabel_text_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    nodes <- isolate({values$group_storage$sem[[group_id]]$nodes})

    title <- "Loop Label(s) to Modify"

    choices <- nodes$node
    choices <- choices[!grepl("^Intercept_", choices)]

    last_residuals <- values$group_storage$sem[[group_id]]$last_residuals

    loop_names_remove <- values$group_storage$sem[[group_id]]$last_loop_names_remove_hi
    if (!is.null(loop_names_remove) && length(loop_names_remove) > 0 &&
        !all(loop_names_remove == "")) {
      choices <- choices[!choices %in% loop_names_remove]
    } else {
      last_residuals_logical <- as.logical(last_residuals %||% FALSE)
      if (last_residuals_logical == FALSE) {
        choices <- NULL
      }
    }

    selected <- isolate({values$group_storage$sem[[group_id]]$last_params_looplabel_text}) %||% character(0)

    if (is.null(choices) || length(choices) == 0) {
      choices <- "No available loop arrows"
    }

    selectizeInput(
      "param_looplabel_text_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select loop arrow(s)...'
      )
    )
  })


  # loop_names_remove_list <- reactiveVal(character(0))

  output$loop_removal_selector <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)

    if (is.null(group_id) || group_id == "" || is.na(group_id)) {
      return(NULL)
    }

    settings <- isolate(values$group_storage$sem[[group_id]])

    if (is.null(settings)) {
      return(NULL)
    }

    nodes <- if (!is.null(settings$nodes)) settings$nodes$node else character(0)
    nodes <- nodes[!grepl("^Intercept_", nodes)]

    # Get current selection from storage
    current_selection <- if (!is.null(settings$last_loop_names_remove_ui)) {
      settings$last_loop_names_remove_ui
    } else {
      character(0)
    }

    selectizeInput(
      "loop_nodes_to_remove",
      "Select loops to remove:",
      choices = nodes,
      selected = current_selection,
      multiple = TRUE,
      options = list(
        placeholder = 'Select nodes...'
      )
    )
  })

  observeEvent(input$clear_loop_removal_list, {
    group_id <- as.character(input$group_select)

    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      # Update storage
      temp_sem <- values$group_storage$sem
      if (!is.null(temp_sem[[group_id]])) {
        temp_sem[[group_id]]$last_loop_names_remove_ui <- character(0)
        values$group_storage$sem <- temp_sem
      }

      # Update UI
      updateSelectizeInput(session, "loop_nodes_to_remove", selected = character(0))
      showNotification("Removal list cleared", type = "message", duration = 2)
    }
  })


  observeEvent(input$loop_nodes_to_remove, {
    group_id <- as.character(input$group_select)

    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      # Update storage
      temp_sem <- values$group_storage$sem
      if (!is.null(temp_sem[[group_id]])) {
        temp_sem[[group_id]]$last_loop_names_remove_ui <- input$loop_nodes_to_remove
        values$group_storage$sem <- temp_sem
      }
    }

  })

  observeEvent(input$clear_param_node_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_node_select", selected = character(0))
      showNotification("Node list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_node_xy_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_node_xy_select", selected = character(0))
      showNotification("Node XY list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_edge_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edge_select", selected = character(0))
      showNotification("Edge list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_cov_edge_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_cov_edge_select", selected = character(0))
      showNotification("Covariance list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_edge_xy_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edge_xy_select", selected = character(0))
      showNotification("Edge XY list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_nodelabel_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_nodelabel_select", selected = character(0))
      showNotification("Node label list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_nodelabel_xy_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_nodelabel_xy_select", selected = character(0))
      showNotification("Node label XY list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_nodelabel_text_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_nodelabel_text_select", selected = character(0))
      showNotification("Node label text list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_edgelabel_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edgelabel_select", selected = character(0))
      showNotification("Edge label list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_edgelabel_xy_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edgelabel_xy_select", selected = character(0))
      showNotification("Edge label XY list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_loop_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_loop_select", selected = character(0))
      showNotification("Loop list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_loop_xy_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_loop_xy_select", selected = character(0))
      showNotification("Loop XY list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_loop_location_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_loop_location_select", selected = character(0))
      showNotification("Loop location list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_looplabel_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_looplabel_select", selected = character(0))
      showNotification("Loop label list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_looplabel_xy_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_looplabel_xy_select", selected = character(0))
      showNotification("Loop label XY list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_edgelabel_text_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edgelabel_text_select", selected = character(0))
      showNotification("Edge label list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_looplabel_text_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_looplabel_text_select", selected = character(0))
      showNotification("Loop label list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_node_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_node_network_select", selected = character(0))
      showNotification("Node list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_node_xy_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_node_xy_network_select", selected = character(0))
      showNotification("Node XY list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_edge_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edge_network_select", selected = character(0))
      showNotification("Edge list cleared", type = "message", duration = 2)
    }
  })


  observeEvent(input$clear_param_bezier_edge_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_bezier_edge_network_select", selected = character(0))
      showNotification("Edge list cleared", type = "message", duration = 2)
    }
  })


  observeEvent(input$clear_param_edge_xy_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edge_xy_network_select", selected = character(0))
      showNotification("Edge list cleared", type = "message", duration = 2)
    }
  })


  observeEvent(input$clear_param_nodelabel_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_nodelabel_network_select", selected = character(0))
      showNotification("Node label list cleared", type = "message", duration = 2)
    }
  })

  observeEvent(input$clear_param_nodelabel_xy_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_nodelabel_xy_network_select", selected = character(0))
      showNotification("Node label XY list cleared", type = "message", duration = 2)
    }
  })


  observeEvent(input$clear_param_nodelabel_text_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_nodelabel_text_network_select", selected = character(0))
      showNotification("Node label text list cleared", type = "message", duration = 2)
    }
  })


  observeEvent(input$clear_param_edgelabel_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edgelabel_network_select", selected = character(0))
      showNotification("Edge label list cleared", type = "message", duration = 2)
    }
  })


  observeEvent(input$reset_modify_params_edgelabel_xy_network, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$edgelabel_xy <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_edgelabel_xy_network", value = FALSE)
  })

  observeEvent(input$clear_param_edgelabel_xy_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edgelabel_xy_network_select", selected = character(0))
      showNotification("Edge label XY list cleared", type = "message", duration = 2)
    }
  })

  output$param_node_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    choices <- settings$nodes$node
    selected <- settings$last_param_node_network_select %||% character(0)

    selectizeInput(
      "param_node_network_select",
      "Node(s) to Modify",
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })


  output$param_bezier_edge_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    choices <- paste0(settings$edges$source, ' to ', settings$edges$target)
    selected <- settings$last_param_bezier_edge_network_select %||% character(0)

    selectizeInput(
      "param_bezier_edge_network_select",
      "Edge(s) to Modify",
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge(s)...'
      )
    )
  })


  output$param_edge_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    choices <- paste0(settings$edges$source, ' to ', settings$edges$target)
    selected <- settings$last_param_edge_network_select %||% character(0)

    selectizeInput(
      "param_edge_network_select",
      "Edge(s) to Modify",
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge(s)...'
      )
    )
  })


  output$param_edgelabel_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    choices <- paste0(settings$edges$source, ' to ', settings$edges$target)
    selected <- settings$last_param_edgelabel_network_select %||% character(0)

    selectizeInput(
      "param_edgelabel_network_select",
      "Edge(s) to Modify",
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge(s)...'
      )
    )
  })

  output$param_edgelabel_xy_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    choices <- paste0(settings$edges$source, ' to ', settings$edges$target)
    selected <- settings$last_param_edgelabel_xy_network_select %||% character(0)

    selectizeInput(
      "param_edgelabel_xy_network_select",
      "Edge(s) to Modify",
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge(s)...'
      )
    )
  })

  output$param_node_xy_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    choices <- settings$nodes$node
    selected <- settings$last_param_node_xy_network_select %||% character(0)

    selectizeInput(
      "param_node_xy_network_select",
      "Node(s) Position to Modify",
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })


  output$param_nodelabel_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    choices <- settings$nodes$node
    selected <- settings$last_param_nodelabel_network_select %||% character(0)

    selectizeInput(
      "param_nodelabel_network_select",
      "Node(s) Label to Modify",
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })

  output$param_nodelabel_xy_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    choices <- settings$nodes$node
    selected <- settings$last_param_nodelabel_xy_network_select %||% character(0)

    selectizeInput(
      "param_nodelabel_xy_network_select",
      "Node(s) to Modify",
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })


  output$param_nodelabel_text_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$network[[group_id]]})

    choices <- settings$nodes$node
    selected <- settings$last_param_nodelabel_text_network_select %||% character(0)

    selectizeInput(
      "param_nodelabel_text_network_select",
      "Node(s) to Modify",
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select node(s)...'
      )
    )
  })


  output$param_edgelabel_text_network_select_ui <- renderUI({
    values$force_ui_refresh
    group_id <- as.character(input$group_select)
    edges <- isolate({values$group_storage$network[[group_id]]$edges})

    title <- "Edge Label(s) to Modify"

    choices <- paste0(edges$source, ' to ', edges$target)
    selected <- isolate({values$group_storage$network[[group_id]]$last_params_edgelabel_text_network}) %||% character(0)

    selectizeInput(
      "param_edgelabel_text_network_select",
      title,
      choices = choices,
      selected = selected,
      multiple = TRUE,
      options = list(
        placeholder = 'Select edge label(s)...'
      )
    )
  })

  observeEvent(input$clear_param_edgelabel_text_network_list, {
    group_id <- as.character(input$group_select)
    if (!is.null(group_id) && group_id != "" && !is.na(group_id)) {
      updateSelectizeInput(session, "param_edgelabel_text_network_select", selected = character(0))
      showNotification("Edge label list cleared", type = "message", duration = 2)
    }
  })

  save_state <- function() {
    current_workflow <- capture_complete_workflow(values, include_history = FALSE)

    # NO compression - store uncompressed
    values$undo_stack <- append(values$undo_stack, list(current_workflow))
    values$redo_stack <- list()
  }

  undo <- function() {
    if (length(values$undo_stack) > 0) {
      # Save current state (uncompressed)
      current_workflow <- capture_complete_workflow(values, include_history = FALSE)
      values$redo_stack <- append(values$redo_stack, list(current_workflow))

      # Get last state (already uncompressed)
      last_state <- tail(values$undo_stack, 1)[[1]]
      values$undo_stack <- values$undo_stack[-length(values$undo_stack)]

      restore_complete_state(last_state)  # No decompression needed
    }
  }

  redo <- function() {
    if (length(values$redo_stack) > 0) {
      # Save current state (uncompressed)
      current_workflow <- capture_complete_workflow(values, include_history = FALSE)
      values$undo_stack <- append(values$undo_stack, list(current_workflow))

      # Get next state (already uncompressed)
      next_state <- tail(values$redo_stack, 1)[[1]]
      values$redo_stack <- values$redo_stack[-length(values$redo_stack)]

      restore_complete_state(next_state)  # No decompression needed
    }
  }

  restore_complete_state <- function(state) {
    values$points <- state$visual_elements$points %||% list()
    values$lines <- state$visual_elements$lines %||% list()
    values$loops <- state$visual_elements$loops %||% list()
    values$annotations <- state$visual_elements$annotations %||% list()

    values$group_storage$sem <- state$sem_groups %||% list()
    values$group_storage$network <- state$network_groups %||% list()
    values$group_storage$modifications <- state$modifications$sem %||% list()
    values$group_storage$modifications_network <- state$modifications$network %||% list()

    values$force_ui_refresh <- runif(1)
  }

  add_new_line <- function(new_line_data) {
    expected_columns <- c(
      "x_start", "y_start", "x_end", "y_end", "ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2",
      "curvature_magnitude", "rotate_curvature", "curvature_asymmetry",
      "type", "color", "end_color", "color_type", "gradient_position", "width",
      "alpha", "arrow", "arrow_type", "arrow_size", "two_way", "lavaan", "network", "line_style", "locked", "group"
    )

    missing_columns <- setdiff(expected_columns, colnames(new_line_data))
    if (length(missing_columns) > 0) {
      for (col in missing_columns) {
        new_line_data[[col]] <- NA
      }
    }

    new_line_data <- new_line_data[expected_columns]
    values$lines <- rbind(values$lines, new_line_data)
  }

  observe({
    req(input$lavaan_syntax)

    model <- tryCatch({
      lavaan::lavaanify(input$lavaan_syntax)
    }, error = function(e) {
      showNotification(paste("Error parsing lavaan syntax:", e$message), type = "error")
      return(NULL)
    })

    if (!is.null(model)) {
      observed_vars <- tryCatch({
        unique(setdiff(model$rhs[model$op %in% c("=~", "~", "~~")], model$lhs[model$op == "=~"]))
      }, error = function(e) {
        showNotification("Error extracting observed variables", type = "error")
        return(character(0))
      })
    } else {
      observed_vars <- character(0)
    }

    data_file <- tryCatch({
      if (!is.null(sem_file_data()) && file.exists(sem_file_data()$datapath)) {
        sem_file_data()$datapath
      } else {
        NULL
      }
    }, error = function(e) {
      showNotification("Error accessing data file", type = "error")
      return(NULL)
    })

    data <- tryCatch({
      if (!is.null(data_file) && file.exists(data_file)) {
        read.csv(data_file, check.names = FALSE)
      } else {
        # Generate synthetic data only if we have observed_vars
        if (length(observed_vars) > 0) {
          as.data.frame(matrix(rnorm(100 * length(observed_vars)), nrow = 100))
        } else {
          data.frame() # empty data frame as fallback
        }
      }
    }, error = function(e) {
      showNotification(paste("Error loading data:", e$message), type = "error")
      # Fallback to synthetic data or empty
      if (length(observed_vars) > 0) {
        as.data.frame(matrix(rnorm(100 * length(observed_vars)), nrow = 100))
      } else {
        data.frame()
      }
    })

    # Set column names for synthetic data
    if (ncol(data) == length(observed_vars) && length(observed_vars) > 0) {
      colnames(data) <- observed_vars
    }

    model_state$data <- data

    has_bundle_data <- FALSE
    if (!is.null(values$bundles) && length(values$bundles) > 0) {
      # Check if the first bundle exists and has an object
      if (1 %in% seq_along(values$bundles) &&
          !is.null(values$bundles[[1]]) &&
          !is.null(values$bundles[[1]]$object)) {
        has_bundle_data <- TRUE
      }
    }

    is_there_data$file <- !is.null(data_file) | has_bundle_data
    is_there_data_no_bundle$file <- !is.null(data_file)

    # Safely update select input
    tryCatch({
      if (!is.null(data) && ncol(data) > 0) {
        updateSelectInput(session, "group_var", choices = names(data))
      } else {
        updateSelectInput(session, "group_var", choices = character(0))
      }
    }, error = function(e) {
      showNotification("Error updating group variable selector", type = "error")
    })

  })

  observeEvent(input$group_var, {
    req(model_state$data, debounced_which_group(),  input$group_var)

    group_levels <- unique(model_state$data[[input$group_var]])

    updateSelectInput(session, "group_level", choices = group_levels)
  })



  observeEvent(input$apply_sem_param_change, {
    req(input$group_select)
    req(is(values$group_storage$sem[[input$group_select]]$current)[[1]] == c("lavaan"))

    save_state()
    group_id <- as.character(input$group_select)

    tryCatch({
      selected <- strsplit(input$param_select, " ")[[1]]
      lhs <- selected[1]
      op <- selected[2]
      rhs <- if (length(selected) >= 3) selected[3] else NULL

      if (!(length(selected) %in% c(2, 3))) {
        stop("Invalid parameter selection")
      }

      settings <- isolate({values$group_storage$sem[[group_id]]})
      pt <- parTable(settings$current)

      has_group_column <- "group" %in% names(pt)

      group_number <- 1

      if (has_group_column && length(unique(pt$group)) > 1) {
        # Multigroup model
        group_info <- lavInspect(settings$current, "group.label")
        group_level <- settings$last_group_level

        # SAFEGUARD: Check if group_level exists
        if (!is.null(group_level) && group_level %in% group_info) {
          group_number <- which(group_info == group_level)
        } else {
          # Use first group as fallback
          group_number <- 1
          # warning("Group level not found, using first group")
        }
      }

      # Find target parameter with or without group condition
      if (length(selected) == 3) { # parameters with rhs
        if (has_group_column) {
          target <- which(
            pt$lhs == lhs &
              pt$op == op &
              pt$rhs == rhs &
              pt$group == group_number
          )
        } else {
          target <- which(
            pt$lhs == lhs &
              pt$op == op &
              pt$rhs == rhs
          )
        }
      } else if (length(selected) == 2) { # intercepts (no rhs)
        if (has_group_column) {
          target <- which(
            pt$lhs == lhs &
              pt$op == op &
              pt$group == group_number
          )
        } else {
          target <- which(
            pt$lhs == lhs &
              pt$op == op
          )
        }
      }

      if(length(target) == 0) {
        # Try without group constraint if still not found
        if (has_group_column) {
          target <- which(
            pt$lhs == lhs &
              pt$op == op &
              (if (!is.null(rhs)) pt$rhs == rhs else TRUE)
          )
        }
        if(length(target) == 0) stop("Parameter not found in model")
      }

      pt[target, "free"] <- 0          # Fix parameter
      pt[target, "ustart"] <- input$param_value  # Set value

      showModal(modalDialog(
        title = "Updating Model",
        "Please wait while the model refits...",
        footer = NULL
      ))

      # Refit model with new constraint
      fit_options <- settings$current@Options
      new_fit <- lavaan(
        model = pt,
        data = settings$data,
        group = lavInspect(settings$current, "group"),
        missing = fit_options$missing,
        estimator = fit_options$estimator,
        se = fit_options$se,
        test = fit_options$test,
        meanstructure = fit_options$meanstructure,
        fixed.x = fit_options$fixed.x,
        orthogonal = fit_options$orthogonal,
        verbose = FALSE
      )

      new_fit@ParTable$est[target] <- new_fit@ParTable$ustart[target] # fixed value, so no p-value


      values$group_storage$sem[[group_id]]$current  <- new_fit

      lavaan_change_click$n_count <- lavaan_change_click$n_count + 1


      showNotification(
        HTML(paste("Successfully updated", input$param_select, "to", input$param_value, ". Click <b>Apply Changes</b> to update the plot!")),
        type = "message"
      )

      updateSelectInput(
        session,
        "param_select",
        choices = paste(
          parameterEstimates(values$group_storage$sem[[group_id]]$current)$lhs,
          parameterEstimates(values$group_storage$sem[[group_id]]$current)$op,
          parameterEstimates(values$group_storage$sem[[group_id]]$current)$rhs
        )
      )

    }, error = function(e) {
      showNotification(
        paste("Failed to update parameter:", e$message),
        type = "error"
      )
    }, finally = {
      removeModal()  # Remove loading spinner
    })


    output$lrt_test <- renderPrint({
      req(input$show_lrt_stats)

      group_id <- as.character(input$group_select)

      current_fit <- values$group_storage$sem[[group_id]]$current
      original_fit <- values$group_storage$sem[[group_id]]$original

      tryCatch({
        lrt_result <- lavTestLRT(original_fit, current_fit)

        cat("LIKELIHOOD RATIO TEST\n")
        cat("=====================\n\n")

        comp <- as.data.frame(lrt_result)[2, ]

        cat("MODEL COMPARISON:\n")
        cat("• Original Model: All parameters free\n")
        cat("• Constrained Model: Parameter(s) fixed\n\n")

        cat("TEST RESULTS:\n")
        cat("-------------\n")
        cat(sprintf("Chi-square difference: %8.3f\n", comp$`Chisq diff`))
        cat(sprintf("Degrees of freedom:    %8d\n", comp$`Df diff`))
        cat(sprintf("p-value:               %8.4f\n", comp$`Pr(>Chisq)`))
        cat(sprintf("RMSEA:                 %8.4f\n", comp$RMSEA))

        cat("\nMODEL FIT INDICES:\n")
        cat("------------------\n")
        cat(sprintf("Original Model AIC:  %8.1f\n", lrt_result[1, "AIC"]))
        cat(sprintf("Constrained Model AIC: %8.1f\n", lrt_result[2, "AIC"]))
        cat(sprintf("Original Model BIC:  %8.1f\n", lrt_result[1, "BIC"]))
        cat(sprintf("Constrained Model BIC: %8.1f\n", lrt_result[2, "BIC"]))

        cat("\nINTERPRETATION:\n")
        cat("---------------\n")

        if(comp$`Pr(>Chisq)` < 0.05) {
          cat("✓ SIGNIFICANT difference (p < 0.05)\n")
          cat("→ The constraint significantly worsens model fit\n")
          cat("→ The constrained parameter should remain free\n")
        } else {
          cat("✓ NO significant difference (p >= 0.05)\n")
          cat("→ The constraint does not worsen model fit\n")
          cat("→ The constraint may be reasonable\n")
        }

      }, error = function(e) {
        cat("Error in LRT calculation:\n", e$message)
      })
    })

    output$fitStatsOutput <- renderPrint({
      req(input$show_fit_stats)

      measures <- c()
      if (input$chisq) measures <- c(measures, "chisq", "df", "pvalue")
      if (input$cfi_tli) measures <- c(measures, "cfi", "tli")
      if (input$rmsea) measures <- c(measures, "rmsea", "rmsea.ci.lower", "rmsea.ci.upper")
      if (input$srmr) measures <- c(measures, "srmr")

      if (input$ppp) measures <- c(measures, "ppp")
      if (input$dic) measures <- c(measures, "dic")
      if (input$waic) measures <- c(measures, "waic")
      if (input$looic) measures <- c(measures, "looic")

      if (length(measures) > 0) {
        stats <- fitMeasures(values$group_storage$sem[[group_id]]$current, measures)

        cat("Model Fit Statistics:\n\n")

        # Bayesian fit measures
        if (is_blavaan()) {
          if (input$ppp && "ppp" %in% names(stats)) {
            cat(sprintf("Posterior Predictive P-value (PPP) = %.3f\n", stats["ppp"]))
          }
          if (input$dic && "dic" %in% names(stats)) {
            cat(sprintf("DIC = %.3f\n", stats["dic"]))
          }
          if (input$waic && "waic" %in% names(stats) ) {
            cat(sprintf("WAIC = %.3f\n", stats["waic"]))
          }
          if (input$looic && "looic" %in% names(stats)) {
            cat(sprintf("LOOIC = %.3f\n", stats["looic"]))
          }
        } else {
          if (input$chisq && "chisq" %in% names(stats)) {
            cat(sprintf("χ²(%d) = %.2f, p = %.3f\n",
                        stats["df"], stats["chisq"], stats["pvalue"]))
          }
          if (input$cfi_tli && "cfi" %in% names(stats)) {
            cat(sprintf("CFI = %.3f\nTLI = %.3f\n", stats["cfi"], stats["tli"]))
          }
          if (input$rmsea && "rmsea" %in% names(stats)) {
            cat(sprintf("RMSEA = %.3f [%.3f, %.3f]\n",
                        stats["rmsea"], stats["rmsea.ci.lower"], stats["rmsea.ci.upper"]))
          }
          if (input$srmr && "srmr" %in% names(stats)) {
            cat(sprintf("SRMR = %.3f\n", stats["srmr"]))
          }
        }

      } else {
        cat("No fit statistics selected")
      }
    })
  })


  output$sig_diff_output <- renderPrint({
    req(sig_diff_results())

    df <- sig_diff_results()
    cat("SIGNIFICANT GROUP DIFFERENCES\n")
    cat("===============================\n")
    cat("Individual parameter Wald tests between groups\n")
    cat("H₀: θ₁ = θ₂\n\n")

    if (!is.data.frame(df) || nrow(df) == 0) {
      cat("No significant differences found.\n")
      return()
    }

    # Format for printing
    df_formatted <- df
    df_formatted$p_value <- format.pval(df_formatted$p_value, digits = 3)

    # Add significance stars
    df_formatted$sig <- sapply(df$p_value, function(p) {
      if (p < 0.001) "***"
      else if (p < 0.01) "**"
      else if (p < 0.05) "*"
      else ""
    })

    df_formatted$significant <- NULL
    names(df_formatted) <- c("From", "", "To", "Comparison", "p_value",  "*")

    # Print the dataframe
    print(df_formatted)

    cat("\n---\n")
    cat("Significance codes: *** p < .001, ** p < .01, * p < .05\n")
  })

  output$lrt_test_multigroup <- renderPrint({
    req(input$show_lrt_stats_multigroup, input$sem_model_type, input$group_var)

    model_type <- input$sem_model_type
    group_var <- input$group_var
    lavaan_string <- input$lavaan_syntax
    data <- model_state$data

    # Choose appropriate function based on model type
    configural <- switch(model_type,
                         "sem" = sem(lavaan_string, data = data, group = group_var),
                         "cfa" = cfa(lavaan_string, data = data, group = group_var),
                         "growth" = growth(lavaan_string, data = data, group = group_var),
                         "efa" = {
                           warning("EFA typically doesn't support multi-group analysis. Using CFA instead.")
                           cfa(lavaan_string, data = data, group = group_var)
                         },
                         # Default to sem
                         sem(lavaan_string, data = data, group = group_var)
    )

    metric <- switch(model_type,
                     "sem" = sem(lavaan_string, data = data, group = group_var, group.equal = "loadings"),
                     "cfa" = cfa(lavaan_string, data = data, group = group_var, group.equal = "loadings"),
                     "growth" = growth(lavaan_string, data = data, group = group_var, group.equal = "loadings"),
                     "efa" = {
                       warning("EFA typically doesn't support multi-group analysis. Using CFA instead.")
                       cfa(lavaan_string, data = data, group = group_var, group.equal = "loadings")
                     },
                     # Default to sem
                     sem(lavaan_string, data = data, group = group_var, group.equal = "loadings")
    )

    scalar <- switch(model_type,
                     "sem" = sem(lavaan_string, data = data, group = group_var, group.equal = c("loadings", "intercepts")),
                     "cfa" = cfa(lavaan_string, data = data, group = group_var, group.equal = c("loadings", "intercepts")),
                     "growth" = growth(lavaan_string, data = data, group = group_var, group.equal = c("loadings", "intercepts")),
                     "efa" = {
                       warning("EFA typically doesn't support multi-group analysis. Using CFA instead.")
                       cfa(lavaan_string, data = data, group = group_var, group.equal = c("loadings", "intercepts"))
                     },
                     # Default to sem
                     sem(lavaan_string, data = data, group = group_var, group.equal = c("loadings", "intercepts"))
    )

    tryCatch({
      lrt_result <- lavTestLRT(configural, metric, scalar)

      cat("MULTI-GROUP LIKELIHOOD RATIO TESTS\n")
      cat("==================================\n\n")

      cat("MODEL COMPARISONS:\n")
      cat("• Configural Model: No constraints across groups\n")
      cat("• Metric Model: Factor loadings constrained equal across groups\n")
      cat("• Scalar Model: Loadings and intercepts constrained equal across groups\n\n")

      cat("TEST RESULTS:\n")
      cat("-------------\n")

      # Configural vs Metric
      comp1 <- as.data.frame(lrt_result)[2, ]
      cat("1. CONFIGURAL vs METRIC (Measurement Invariance):\n")
      cat(sprintf("   Chi-square difference: %8.3f\n", comp1$`Chisq diff`))
      cat(sprintf("   Degrees of freedom:    %8d\n", comp1$`Df diff`))
      cat(sprintf("   p-value:               %8.4f\n", comp1$`Pr(>Chisq)`))

      # Metric vs Scalar
      comp2 <- as.data.frame(lrt_result)[3, ]
      cat("\n2. METRIC vs SCALAR (Scalar Invariance):\n")
      cat(sprintf("   Chi-square difference: %8.3f\n", comp2$`Chisq diff`))
      cat(sprintf("   Degrees of freedom:    %8d\n", comp2$`Df diff`))
      cat(sprintf("   p-value:               %8.4f\n", comp2$`Pr(>Chisq)`))

      cat("\nMODEL FIT INDICES:\n")
      cat("------------------\n")
      cat(sprintf("Configural Model AIC: %8.1f\n", lrt_result[1, "AIC"]))
      cat(sprintf("Metric Model AIC:     %8.1f\n", lrt_result[2, "AIC"]))
      cat(sprintf("Scalar Model AIC:     %8.1f\n", lrt_result[3, "AIC"]))
      cat(sprintf("Configural Model BIC: %8.1f\n", lrt_result[1, "BIC"]))
      cat(sprintf("Metric Model BIC:     %8.1f\n", lrt_result[2, "BIC"]))
      cat(sprintf("Scalar Model BIC:     %8.1f\n", lrt_result[3, "BIC"]))

      cat("\nINTERPRETATION:\n")
      cat("---------------\n")

      # Configural vs Metric interpretation
      if(comp1$`Pr(>Chisq)` < 0.05) {
        cat("• Configural vs Metric: SIGNIFICANT difference (p < 0.05)\n")
        cat("  → Metric invariance NOT supported\n")
        cat("  → Factor loadings differ across groups\n")
      } else {
        cat("• Configural vs Metric: NO significant difference (p >= 0.05)\n")
        cat("  → Metric invariance supported\n")
        cat("  → Factor loadings are equivalent across groups\n")
      }

      # Metric vs Scalar interpretation
      if(comp2$`Pr(>Chisq)` < 0.05) {
        cat("• Metric vs Scalar: SIGNIFICANT difference (p < 0.05)\n")
        cat("  → Scalar invariance NOT supported\n")
        cat("  → Intercepts differ across groups\n")
      } else {
        cat("• Metric vs Scalar: NO significant difference (p >= 0.05)\n")
        cat("  → Scalar invariance supported\n")
        cat("  → Both loadings and intercepts are equivalent across groups\n")
      }

    }, error = function(e) {
      cat("Error in LRT calculation:\n", e$message)
    })
  })

  observeEvent(input$reset_node_shift, {
    updateNumericInput(session, "sem_node_shift_x", value = 0)
    updateNumericInput(session, "sem_node_shift_y", value = 0)
  })

  observeEvent(input$reset_latent_node_shift, {
    updateNumericInput(session, "sem_latent_node_shift_x", value = 0)
    updateNumericInput(session, "sem_latent_node_shift_y", value = 0)
  })

  observeEvent(input$reset_latent_node_angle, {
    updateNumericInput(session, "sem_latent_node_angle", value = 0)
  })

  observeEvent(input$reset_edgelabel_xy_shift, {
    updateNumericInput(session, "modify_params_edgelabel_shift_x", value = 0)
    updateNumericInput(session, "modify_params_edgelabel_shift_y", value = 0)
  })

  observeEvent(input$reset_edge_cov_shift, {
    updateNumericInput(session, "param_cov_edge_x_shift", value = 0)
    updateNumericInput(session, "param_cov_edge_y_shift", value = 0)
  })

  observeEvent(input$reset_edgelabel_xy_shift_network, {
    updateNumericInput(session, "modify_params_edgelabel_network_shift_x", value = 0)
    updateNumericInput(session, "modify_params_edgelabel_network_shift_y", value = 0)
  })


  observeEvent(input$reset_nodelabel_shift, {
    updateNumericInput(session, "modify_params_nodelabel_shift_x", value = 0)
    updateNumericInput(session, "modify_params_nodelabel_shift_y", value = 0)
  })

  observeEvent(input$reset_nodelabel_shift_network, {
    updateNumericInput(session, "modify_params_nodelabel_network_shift_x", value = 0)
    updateNumericInput(session, "modify_params_nodelabel_network_shift_y", value = 0)
  })

  observeEvent(input$reset_edge_shift, {
    updateNumericInput(session, "modify_params_edge_start_shift_x", value = 0)
    updateNumericInput(session, "modify_params_edge_start_shift_y", value = 0)
    updateNumericInput(session, "modify_params_edge_end_shift_x", value = 0)
    updateNumericInput(session, "modify_params_edge_end_shift_y", value = 0)
  })

  observeEvent(input$reset_edge_shift_network, {
    updateNumericInput(session, "modify_params_edge_network_start_shift_x", value = 0)
    updateNumericInput(session, "modify_params_edge_network_start_shift_y", value = 0)
    updateNumericInput(session, "modify_params_edge_network_end_shift_x", value = 0)
    updateNumericInput(session, "modify_params_edge_network_end_shift_y", value = 0)
  })

  observeEvent(input$reset_group_shift, {
    updateNumericInput(session, "group_shift_x", value = 0)
    updateNumericInput(session, "group_shift_y", value = 0)
  })



  observeEvent(input$reset_modify_params_node, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$node <- data.frame(
      text = character(),
      color = character(),
      alpha = numeric(),
      shape = character(),
      size = numeric(),
      border_color = character(),
      border_width = numeric(),
      width_height_ratio = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_node", value = FALSE)
  })



  observeEvent(input$reset_modify_params_node_xy, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$node_xy <- data.frame(
      text = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_node_xy", value = FALSE)
  })

  observeEvent(input$reset_modify_params_latent_node_xy, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$latent_node_xy <- data.frame(
      text = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      node_type = character(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_latent_node_xy", value = FALSE)
  })

  observeEvent(input$reset_modify_params_latent_node_angle, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$latent_node_angle <- data.frame(
      text = character(),
      angle = numeric(),
      node_type = character(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_latent_node_angle", value = FALSE)
  })


  observeEvent(input$reset_modify_params_edge, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$edge <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      color = character(),
      width = numeric(),
      alpha = numeric(),
      line_style = character(),
      color_type = character(),
      end_color = character(),
      gradient_position = character(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_edge", value = FALSE)
  })


  observeEvent(input$reset_modify_params_cov_edge, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$cov_edge <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      curvature_magnitude = numeric(),
      rotate_curvature = logical(),
      curvature_asymmetry = numeric(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_cov_edge", value = FALSE)
  })



  observeEvent(input$reset_modify_params_edge_xy, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$edge_xy <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      start_x_shift = numeric(),
      start_y_shift = numeric(),
      end_x_shift = numeric(),
      end_y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_edge_xy", value = FALSE)
  })



  observeEvent(input$reset_modify_params_nodelabel, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$nodelabel <- data.frame(
      text = character(),
      color = character(),
      size = numeric(),
      alpha = numeric(),
      angle = numeric(),
      font = character(),
      fontface = character(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_nodelabel", value = FALSE)
  })


  observeEvent(input$reset_modify_params_nodelabel_xy, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$nodelabel_xy <- data.frame(
      text = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_nodelabel_xy", value = FALSE)
  })



  observeEvent(input$reset_modify_params_nodelabel_text, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$nodelabel_text <- data.frame(
      text = character(),
      nodelabel = character(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_nodelabel_text", value = FALSE)
  })



  observeEvent(input$reset_modify_params_edgelabel, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$edgelabel <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      color = character(),
      fill = character(),
      size = numeric(),
      alpha = numeric(),
      angle = numeric(),
      font = character(),
      fontface = character(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_edgelabel", value = FALSE)
  })



  observeEvent(input$reset_modify_params_edgelabel_xy, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$edgelabel_xy <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_edgelabel_xy", value = FALSE)
  })

  observeEvent(input$reset_modify_params_loop, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$loop <- data.frame(
      text = character(),
      color = character(),
      alpha = numeric(),
      radius = numeric(),
      width = numeric(),
      type = character(),
      arrow_size = numeric(),
      gap_size = numeric(),
      two_way = logical(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"), type = "message")
    updateCheckboxInput(session, "modify_params_loop", value = FALSE)
  })

  observeEvent(input$reset_modify_params_loop_xy, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$loop_xy <- data.frame(
      text = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"), type = "message")
    updateCheckboxInput(session, "modify_params_loop_xy", value = FALSE)
  })

  observeEvent(input$reset_modify_params_loop_location, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$loop_location <- data.frame(
      text = character(),
      loop_location = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"), type = "message")
    updateCheckboxInput(session, "modify_params_loop_location", value = FALSE)
  })

  observeEvent(input$reset_modify_params_looplabel, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$looplabel <- data.frame(
      text = character(),
      color = character(),
      size = numeric(),
      alpha = numeric(),
      angle = numeric(),
      font = character(),
      fontface = character(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"), type = "message")
    updateCheckboxInput(session, "modify_params_looplabel", value = FALSE)
  })

  observeEvent(input$reset_modify_params_looplabel_xy, { # SEM
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications[[group_id]]$looplabel_xy <- data.frame(
      text = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"), type = "message")
    updateCheckboxInput(session, "modify_params_looplabel_xy", value = FALSE)
  })


  observeEvent(input$reset_modify_params_node_network, {
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$node <- data.frame(
      text = character(),
      color = character(),
      alpha = numeric(),
      shape = character(),
      size = numeric(),
      border_color = character(),
      border_width = numeric(),
      width_height_ratio = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_node_network", value = FALSE)
  })

  observeEvent(input$reset_modify_params_node_xy_network, {
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$node_xy <- data.frame(
      text = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_node_xy_network", value = FALSE)
  })


  observeEvent(input$reset_modify_params_edge_network, {
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$edge <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      color = character(),
      width = numeric(),
      alpha = numeric(),
      line_style = character(),
      color_type = character(),
      end_color = character(),
      gradient_position = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_edge_network", value = FALSE)
  })

  observeEvent(input$reset_modify_params_bezier_network_edges, {
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$edge_curvature <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      curvature_magnitude = numeric(),
      rotate_curvature = logical(),
      curvature_asymmetry = numeric(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_bezier_network_edges", value = FALSE)
  })


  observeEvent(input$reset_modify_params_edge_xy_network, {
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$edge_xy <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      start_x_shift = numeric(),
      start_y_shift = numeric(),
      end_x_shift = numeric(),
      end_y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_edge_xy_network", value = FALSE)
  })


  observeEvent(input$reset_modify_params_nodelabel_network, {
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$nodelabel <- data.frame(
      text = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_nodelabel_network", value = FALSE)
  })



  observeEvent(input$reset_modify_params_nodelabel_xy_network, {
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$nodelabel_xy <- data.frame(
      text = character(),
      x_shift = numeric(),
      y_shift = numeric(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_nodelabel_xy_network", value = FALSE)
  })



  observeEvent(input$reset_modify_params_nodelabel_text_network, {
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$nodelabel_text <- data.frame(
      text = character(),
      nodelabel = character(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_nodelabel_text_network", value = FALSE)
  })


  observeEvent(input$reset_modify_params_edgelabel_network, {
    req(input$group_select)
    group_id <- input$group_select
    save_state()

    values$group_storage$modifications_network[[group_id]]$edgelabel <- data.frame(
      lhs = character(),
      op = character(),
      rhs = character(),
      color = character(),
      fill = character(),
      size = numeric(),
      alpha = numeric(),
      angle = numeric(),
      font = character(),
      fontface = character(),
      stringsAsFactors = FALSE
    )

    showNotification(HTML("Changes have been resetted. Click <b>Apply Changes</b> to update the plot!"),   type = "message" )
    updateCheckboxInput(session, "modify_params_edgelabel_network", value = FALSE)
  })

  observeEvent(input$reset_sem_model, {
    req(input$group_select)  # Ensure original exists

    group_id <- as.character(input$group_select)
    settings <- isolate({values$group_storage$sem[[group_id]]})

    # Show loading state
    showModal(modalDialog("Reverting to original model...", footer = NULL))

    values$group_storage$sem[[group_id]]$current <- settings$original

    # Update UI elements
    updateSelectInput(
      session,
      "param_select",
      choices = paste(parameterEstimates(settings$original)$lhs,
                      parameterEstimates(settings$original)$op,
                      parameterEstimates(settings$original)$rhs)
    )

    removeModal()
    showNotification("Model reset to original state", type = "message")
  })

  data_table_proxy <- dataTableProxy("data_table")

  observeEvent(input$unlock_points, {
    tryCatch({
      if (any(values$points$locked)) {
        save_state()
        values$points$locked <- FALSE
        showNotification("All points have been unlocked.", type = "message")
      } else {
        showNotification("No locked points to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking points:", e$message), type = "error")
    })
  })

  observeEvent(input$lock_points, {
    tryCatch({
      if (any(!values$points$locked)) {
        save_state()
        values$points$locked <- TRUE
        showNotification("All points have been locked.", type = "message")
      } else {
        showNotification("No unlocked points to lock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error locking points:", e$message), type = "error")
    })
  })

  observeEvent(input$unlock_selected_point, {
    tryCatch({
      selected_row <- input$data_table_rows_selected
      if (!is.null(selected_row)) {
        save_state()
        values$points$locked[selected_row] <- FALSE
        showNotification(
          paste("Points at rows", paste(selected_row, collapse = ", "), "have been unlocked."),
          type = "message"
        )
      } else {
        showNotification("No point selected. Please select a point to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking selected points:", e$message), type = "error")
    })
  })

  observeEvent(input$unlock_selected_point, {
    tryCatch({
      selected_row <- input$data_table_rows_selected
      if (!is.null(selected_row)) {
        save_state()
        values$points$locked[selected_row] <- FALSE
        showNotification(
          paste("Points at rows", paste(selected_row, collapse = ", "), "have been unlocked."),
          type = "message"
        )
      } else {
        showNotification("No point selected. Please select a point to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking selected points:", e$message), type = "error")
    })
  })

  observeEvent(input$unlock_selected_point, {
    tryCatch({
      selected_row <- input$data_table_rows_selected
      if (!is.null(selected_row)) {
        save_state()
        values$points$locked[selected_row] <- FALSE
        showNotification(
          paste("Points at rows", paste(selected_row, collapse = ", "), "have been unlocked."),
          type = "message"
        )
      } else {
        showNotification("No point selected. Please select a point to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking selected points:", e$message), type = "error")
    })
  })

  observeEvent(input$finalize_sem_point, {
    tryCatch({
      selected_row <- input$data_table_rows_selected
      if (!is.null(selected_row)) {
        save_state()
        values$points$lavaan[selected_row] <- FALSE
        showNotification(
          paste("SEM nodes at rows", paste(selected_row, collapse = ", "), "have been finalized."),
          type = "message"
        )
      } else {
        showNotification("No node selected. Please select a node to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking selected nodes:", e$message), type = "error")
    })
  })

  observeEvent(input$finalize_network_point, {
    tryCatch({
      selected_row <- input$data_table_rows_selected
      if (!is.null(selected_row)) {
        save_state()
        values$points$network[selected_row] <- FALSE
        showNotification(
          paste("Network nodes at rows", paste(selected_row, collapse = ", "), "have been finalized."),
          type = "message"
        )
      } else {
        showNotification("No node selected. Please select a node to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking selected nodes:", e$message), type = "error")
    })
  })

  observeEvent(input$lock_selected_point, {
    tryCatch({
      selected_row <- input$data_table_rows_selected
      if (!is.null(selected_row)) {
        save_state()
        values$points$locked[selected_row] <- TRUE
        showNotification(
          paste("Points at rows", paste(selected_row, collapse = ", "), "have been locked."),
          type = "message"
        )
      } else {
        showNotification("No point selected. Please select a point to lock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error locking selected points:", e$message), type = "error")
    })
  })

  observeEvent(input$unlock_annotations_button, {
    tryCatch({
      if (any(values$annotations$locked)) {
        save_state()
        values$annotations$locked <- FALSE
        showNotification("All annotations have been unlocked.", type = "message")
      } else {
        showNotification("No locked annotations to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking annotations:", e$message), type = "error")
    })
  })

  observeEvent(input$lock_annotations_button, {
    tryCatch({
      if (any(!values$annotations$locked)) {
        save_state()
        values$annotations$locked <- TRUE
        showNotification("All annotations have been locked.", type = "message")
      } else {
        showNotification("No unlocked annotations to lock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error locking annotations:", e$message), type = "error")
    })
  })

  observeEvent(input$unlock_lines_button, {
    tryCatch({
      if (any(values$lines$locked)) {
        save_state()
        values$lines$locked <- FALSE
        showNotification("All lines have been unlocked.", type = "message")
      } else {
        showNotification("No locked lines to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking lines:", e$message), type = "error")
    })
  })

  observeEvent(input$lock_lines_button, {
    tryCatch({
      if (any(!values$lines$locked)) {
        save_state()
        values$lines$locked <- TRUE
        showNotification("All lines have been locked.", type = "message")
      } else {
        showNotification("No unlocked lines to lock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error locking lines:", e$message), type = "error")
    })
  })


  observeEvent(input$lock_loops, {
    tryCatch({
      if (any(!values$loops$locked)) {
        save_state()
        values$loops$locked <- TRUE
        showNotification("All self-loop arrows have been locked.", type = "message")
      } else {
        showNotification("No unlocked self-loop arrows to lock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error locking self-loop arrows:", e$message), type = "error")
    })
  })

  observeEvent(input$unlock_selected_loop, {
    tryCatch({
      selected_row <- input$loop_table_rows_selected
      if (!is.null(selected_row)) {
        save_state()
        values$loops$locked[selected_row] <- FALSE
        showNotification(
          paste("Self-loop arrow at row", paste(selected_row, collapse = ", "), "has been unlocked."),
          type = "message"
        )
      } else {
        showNotification("No self-loop arrow selected. Please select a self-loop arrow to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking self-loop arrows:", e$message), type = "error")
    })
  })

  observeEvent(input$lock_selected_loop, {
    tryCatch({
      selected_row <- input$loop_table_rows_selected
      if (!is.null(selected_row)) {
        save_state()
        values$loops$locked[selected_row] <- TRUE
        showNotification(
          paste("Self-loop arrow at row", paste(selected_row, collapse = ", "), "has been locked."),
          type = "message"
        )
      } else {
        showNotification("No self-loop arrow selected. Please select a self-loop arrow to lock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error locking self-loop arrows:", e$message), type = "error")
    })
  })

  observeEvent(input$unlock_all_loops, {
    tryCatch({
      if (nrow(values$loops) > 0) {
        save_state()
        values$loops$locked <- FALSE
        showNotification("All self-loop arrows have been unlocked.", type = "message")
      } else {
        showNotification("No self-loop arrows available to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error unlocking all self-loop arrows:", e$message), type = "error")
    })
  })

  observeEvent(input$lock_all_loops, {
    tryCatch({
      if (nrow(values$loops) > 0) {
        save_state()
        values$loops$locked <- TRUE
        showNotification("All self-loop arrows have been locked.", type = "message")
      } else {
        showNotification("No self-loop arrows available to lock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(paste("Error locking all self-loop arrows:", e$message), type = "error")
    })
  })



  observeEvent(input$lock_network, {
    req(input$group_select)
    tryCatch({
      save_state()

      group_id <- as.character(input$group_select)

      network_points <- which(values$points$network & values$points$group == input$group_select)
      network_lines <- which(values$lines$network & values$lines$group == input$group_select)
      network_annotations <- which(values$annotations$network & values$annotations$group == input$group_select)

      # Lock Network elements
      values$points$network[network_points] <- FALSE
      values$lines$network[network_lines] <- FALSE
      values$annotations$network[network_annotations] <- FALSE

      showNotification(
        HTML(paste("Successfully finalized", "<b>Network</b>", "visualization in group", "<b>", group_id, "</b>. Please use <b>'Aesthetic Grouping'<b> menu.")),
        type = "message"
      )
    }, error = function(e) {
      showNotification(paste("Error locking network elements:", e$message), type = "error")
    })
  })


  observeEvent(input$lock_lavaan, {
    req(input$group_select)
    tryCatch({
      save_state()

      group_id <- as.character(input$group_select)

      lavaan_points <- which(values$points$lavaan & values$points$group == input$group_select)
      lavaan_lines <- which(values$lines$lavaan & values$lines$group == input$group_select)
      lavaan_annotations <- which(values$annotations$lavaan & values$annotations$group == input$group_select)

      # Lock SEM elements
      values$points$lavaan[lavaan_points] <- FALSE
      values$lines$lavaan[lavaan_lines] <- FALSE
      values$annotations$lavaan[lavaan_annotations] <- FALSE

      showNotification(
        HTML(paste("Successfully finalized", "<b>SEM</b>", "visualization in group", "<b>", group_id, "</b>. Please use <b>'Aesthetic Grouping'<b> menu.")),
        type = "message"
      )

    }, error = function(e) {
      showNotification(paste("Error locking SEM elements:", e$message), type = "error")
    })
  })

  observeEvent(input$undo_button, {
    undo()
    output$plot <- renderPlot({
      recreate_plot()
    })
  })

  observeEvent(input$redo_button, {
    redo()
    output$plot <- renderPlot({
      recreate_plot()
    })
  })


  observeEvent( # curved line
    {
      input$x_start
      input$y_start
      input$x_end
      input$y_end
      input$curvature_magnitude
      input$rotate_curvature
      input$curvature_asymmetry
      input$line_type
    },
    {
      req(input$x_start, input$y_start, input$x_end, input$y_end, input$curvature_magnitude, input$curvature_asymmetry)

      default_ctrl <- calculate_control_point(x_start = as.numeric(input$x_start),
                                              y_start = as.numeric(input$y_start),
                                              x_end = as.numeric(input$x_end),
                                              y_end = as.numeric(input$y_end),
                                              curvature_magnitude = as.numeric(input$curvature_magnitude),
                                              rotate_curvature = as.logical(input$rotate_curvature),
                                              curvature_asymmetry = as.numeric(input$curvature_asymmetry))

      curve_ctrl_values$ctrl_x <- default_ctrl$ctrl_x
      curve_ctrl_values$ctrl_y <- default_ctrl$ctrl_y
      curve_ctrl_values$ctrl_x2 <- default_ctrl$ctrl_x2
      curve_ctrl_values$ctrl_y2 <- default_ctrl$ctrl_y2
    }
  )

  # Add point
  observeEvent(input$add_point, {
    tryCatch({
      req(input$x_coord, input$y_coord)
      save_state() # Save the state before making changes
      new_point <- data.frame(
        x = as.numeric(input$x_coord),
        y = as.numeric(input$y_coord),
        shape = input$shape,
        color = input$point_color,
        size = input$point_size,
        border_color = input$border_color,
        border_width = input$border_width,
        alpha = input$point_alpha,
        width_height_ratio = ifelse(input$shape %in% c("rectangle", "oval", "diamond"),
                                    as.numeric(input$width_height_ratio), 1),
        orientation = as.numeric(input$point_orientation),
        lavaan = FALSE,
        network = FALSE,
        locked = FALSE,
        group = debounced_which_group(),
        stringsAsFactors = FALSE
      )
      values$points <- rbind(values$points, new_point)
      showNotification("Point added successfully.", type = "message", duration = 5)

      output$plot <- renderPlot({
        recreate_plot()
      })

    }, error = function(e) {
      showNotification(
        paste("Error adding point:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  # Auto layout points
  observeEvent(input$auto_layout, {
    tryCatch({
      req(nrow(values$points) > 0)
      save_state()

      values$points <- auto_layout_points(
        values$points,
        layout_type = input$layout_type,
        distance = input$point_distance,
        center_x = input$center_x,
        center_y = input$center_y,
        orientation = input$layout_orientation,
        curvature_magnitude = input$curvature_magnitude,
        flip_curve = input$rotate_curvature,
        asymmetry = input$curvature_asymmetry,
        random_seed = input$random_seed,
        which_group = input$group_select
      )

      output$plot <- renderPlot({
        recreate_plot()
      })

    }, error = function(e) {
      showNotification(
        paste("Error applying auto layout:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observeEvent(input$apply_group_changes, {
    req(input$group_select)
    tryCatch({
      save_state()

      group_id <- as.character(input$group_select)

      if (is.null(values$group_storage$group_settings[[group_id]])) {
        if (is.null(values$group_storage$group_settings)) {
          values$group_storage$group_settings <- list()
        }
        values$group_storage$group_settings[[group_id]] <- create_group_storage(type = "group_settings")
      }

      group_storage <- isolate({values$group_storage$group_settings[[group_id]]})

      xy_shift_applied <- input$group_shift_xy
      aesthetics_changed <- any(input$group_aesthetics_point_only,
                                input$group_aesthetics_line_only,
                                input$group_aesthetics_annotation_only,
                                input$group_aesthetics_loop_only)

      group_shift_xy <- input$group_shift_xy
      group_shift_x <- input$group_shift_x
      group_shift_y <- input$group_shift_y
      shift_x <- 0
      shift_y <- 0

      # group_shift_angle <- input$group_shift_angle
      # rotation_angle <- input$angle_group # angle shift

      group_aesthetics_point_only <- input$group_aesthetics_point_only
      group_aesthetics_line_only <- input$group_aesthetics_line_only
      group_aesthetics_annotation_only <- input$group_aesthetics_annotation_only
      group_aesthetics_loop_only <- input$group_aesthetics_loop_only

      point_color_group <- input$point_color_group
      shape_group <- input$shape_group
      point_size_group <- input$point_size_group
      border_width_group <- input$border_width_group
      border_color_group <- input$border_color_group
      point_alpha_group <- input$point_alpha_group
      point_orientation_group <- input$point_orientation_group
      width_height_ratio_group <- input$width_height_ratio_group

      line_color_group <- input$line_color_group
      color_type_group <- input$color_type_group
      line_width_group <- input$line_width_group
      line_alpha_group <- input$line_alpha_group
      line_style_group <- input$line_style_group
      line_type_group <- input$line_type_group
      two_way_arrow_group <- input$two_way_arrow_group
      arrow_size_group <- input$arrow_size_group
      arrow_type_group <- input$arrow_type_group
      end_color_group <- input$end_color_group
      gradient_position_group <- input$gradient_position_group


      text_font_group <- input$text_font_group
      text_size_group <- input$text_size_group
      text_color_group <- input$text_color_group
      text_fontface_group <- input$text_fontface_group
      text_alpha_group <- input$text_alpha_group
      text_angle_group <- input$text_angle_group
      apply_text_fill_group <- input$apply_text_fill_group
      text_fill_group <- if (apply_text_fill_group) input$text_fill_group else NA


      radius_group <- input$radius_group
      line_width_loop_group <- input$line_width_loop_group
      line_color_loop_group <- input$line_color_loop_group
      line_alpha_loop_group <- input$line_alpha_loop_group
      arrow_type_loop_group <- input$arrow_type_loop_group
      arrow_size_loop_group <- input$arrow_size_loop_group
      width_loop_group <- input$width_loop_group
      height_loop_group <- input$height_loop_group
      two_way_arrow_loop_group <- input$two_way_arrow_loop_group
      orientation_loop_group <- input$orientation_loop_group
      gap_size_loop_group <- input$gap_size_loop_group

      if (group_shift_xy) {
        shift_x <- if (!is.null(group_shift_x) && group_shift_x != "" && !is.na(as.numeric(group_shift_x))) {
          as.numeric(group_shift_x)
        } else {
          0
        }
        shift_y <- if (!is.null(group_shift_y) && group_shift_y != "" && !is.na(as.numeric(group_shift_y))) {
          as.numeric(group_shift_y)
        } else {
          0
        }
      }

      values$group_storage$group_settings[[group_id]]$last_group_shift_xy <- group_shift_xy
      values$group_storage$group_settings[[group_id]]$last_group_shift_x <- group_shift_x
      values$group_storage$group_settings[[group_id]]$last_group_shift_y <- group_shift_y
      values$group_storage$group_settings[[group_id]]$last_position_group_select <- group_id

      modified_elements <- c()

      if (!is.null(values$points)) {
        unlocked_points_idx <- which(!values$points$locked & !values$points$lavaan & !values$points$network & values$points$group == group_id)
        if (length(unlocked_points_idx) > 0) {
          if (group_shift_xy) {
            values$points$x[unlocked_points_idx] <- values$points$x[unlocked_points_idx] + shift_x
            values$points$y[unlocked_points_idx] <- values$points$y[unlocked_points_idx] + shift_y
          }


          if (group_aesthetics_point_only) {
            values$points$color[unlocked_points_idx] <- point_color_group
            values$points$shape[unlocked_points_idx] <- shape_group
            values$points$size[unlocked_points_idx] <- point_size_group
            values$points$border_width[unlocked_points_idx] <- border_width_group
            values$points$border_color[unlocked_points_idx] <- border_color_group
            values$points$alpha[unlocked_points_idx] <- point_alpha_group
            values$points$orientation[unlocked_points_idx] <- point_orientation_group

            if (input$shape %in% c('rectangle', 'oval', 'diamond')) {
              values$points$width_height_ratio[unlocked_points_idx] <- width_height_ratio_group
            } else {
              values$points$width_height_ratio[unlocked_points_idx] <- 1
            }
          }

          modified_elements <- c(modified_elements, paste(length(unlocked_points_idx), "points"))

          values$group_storage$group_settings[[group_id]]$last_point_color_group <- point_color_group
          values$group_storage$group_settings[[group_id]]$last_shape_group <- shape_group
          values$group_storage$group_settings[[group_id]]$last_point_size_group <- point_size_group
          values$group_storage$group_settings[[group_id]]$last_border_width_group <- border_width_group
          values$group_storage$group_settings[[group_id]]$last_border_color_group <- border_color_group
          values$group_storage$group_settings[[group_id]]$last_point_alpha_group <- point_alpha_group
          values$group_storage$group_settings[[group_id]]$last_point_orientation_group <- point_orientation_group
          values$group_storage$group_settings[[group_id]]$last_width_height_ratio_group <- width_height_ratio_group
        }
      }

      if (!is.null(values$lines)) {
        unlocked_lines_idx <- which(!values$lines$locked & !values$lines$lavaan & !values$lines$network & values$lines$group == group_id)
        if (length(unlocked_lines_idx) > 0) {

          if (group_shift_xy) {
            values$lines$x_start[unlocked_lines_idx] <- values$lines$x_start[unlocked_lines_idx] + shift_x
            values$lines$y_start[unlocked_lines_idx] <- values$lines$y_start[unlocked_lines_idx] + shift_y
            values$lines$x_end[unlocked_lines_idx] <- values$lines$x_end[unlocked_lines_idx] + shift_x
            values$lines$y_end[unlocked_lines_idx] <- values$lines$y_end[unlocked_lines_idx] + shift_y

            # curved_lines_idx <- which(unlocked_lines_idx &
            #                             values$lines$type %in% c("Curved Line", "Curved Arrow"))

            curved_lines_idx <- unlocked_lines_idx[values$lines$type[unlocked_lines_idx] %in% c("Curved Line", "Curved Arrow")]

            if (length(curved_lines_idx) > 0) {
              control_points <- mapply(
                calculate_control_point,
                x_start = values$lines$x_start[curved_lines_idx],
                y_start = values$lines$y_start[curved_lines_idx],
                x_end = values$lines$x_end[curved_lines_idx],
                y_end = values$lines$y_end[curved_lines_idx],
                curvature_magnitude = values$lines$curvature_magnitude[curved_lines_idx],
                rotate_curvature = values$lines$rotate_curvature[curved_lines_idx],
                curvature_asymmetry = values$lines$curvature_asymmetry[curved_lines_idx],
                center_x = mean(values$points$x[unlocked_points_idx]),
                center_y = mean(values$points$y[unlocked_points_idx]),
                SIMPLIFY = FALSE
              )

              values$lines$ctrl_x[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_x"), 5)
              values$lines$ctrl_y[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_y"), 5)
              values$lines$ctrl_x2[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_x2"), 5)
              values$lines$ctrl_y2[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_y2"), 5)
            }
          }

          if (group_aesthetics_line_only) {
            values$lines$color[unlocked_lines_idx] <- line_color_group
            values$lines$color_type[unlocked_lines_idx] <- color_type_group
            values$lines$width[unlocked_lines_idx] <- line_width_group
            values$lines$alpha[unlocked_lines_idx] <- line_alpha_group
            values$lines$line_style[unlocked_lines_idx] <- line_style_group
            values$lines$type[unlocked_lines_idx] <- line_type_group
            values$lines$two_way[unlocked_lines_idx] <- two_way_arrow_group

            if (input$line_type %in% c("Straight Arrow", "Curved Arrow")) {
              values$lines$arrow_size[unlocked_lines_idx] <- arrow_size_group
              values$lines$arrow_type[unlocked_lines_idx] <- arrow_type_group
            } else {
              arrow_size_group <- NA
              arrow_type_group <- NA
              values$lines$arrow_size[unlocked_lines_idx] <- arrow_size_group
              values$lines$arrow_type[unlocked_lines_idx] <- arrow_type_group
            }

            if (input$color_type == "Gradient") {
              values$lines$end_color[unlocked_lines_idx] <- end_color_group
              values$lines$gradient_position[unlocked_lines_idx] <- gradient_position_group
            }

          }

          modified_elements <- c(modified_elements, paste(length(unlocked_lines_idx), "lines"))

          values$group_storage$group_settings[[group_id]]$last_line_color_group <- line_color_group
          values$group_storage$group_settings[[group_id]]$last_color_type_group <- color_type_group
          values$group_storage$group_settings[[group_id]]$last_line_width_group <- line_width_group
          values$group_storage$group_settings[[group_id]]$last_line_alpha_group <- line_alpha_group
          values$group_storage$group_settings[[group_id]]$last_line_style_group <- line_style_group
          values$group_storage$group_settings[[group_id]]$last_line_type_group <- line_type_group
          values$group_storage$group_settings[[group_id]]$last_two_way_arrow_group <- two_way_arrow_group
          values$group_storage$group_settings[[group_id]]$last_arrow_size_group <- arrow_size_group
          values$group_storage$group_settings[[group_id]]$last_arrow_type_group <- arrow_type_group
          values$group_storage$group_settings[[group_id]]$last_end_color_group <- end_color_group
          values$group_storage$group_settings[[group_id]]$last_gradient_position_group <- gradient_position_group
        }
      }

      if (!is.null(values$annotations)) {
        unlocked_annotations_idx <- which(!values$annotations$locked & !values$annotations$lavaan & !values$annotations$network & values$annotations$group == group_id)
        if (length(unlocked_annotations_idx) > 0) {
          if (group_shift_xy) {
            values$annotations$x[unlocked_annotations_idx] <- values$annotations$x[unlocked_annotations_idx] + shift_x
            values$annotations$y[unlocked_annotations_idx] <- values$annotations$y[unlocked_annotations_idx] + shift_y
          }


          if (group_aesthetics_annotation_only) {
            values$annotations$font[unlocked_annotations_idx] <- text_font_group
            values$annotations$size[unlocked_annotations_idx] <- text_size_group
            values$annotations$color[unlocked_annotations_idx] <- text_color_group
            values$annotations$fontface[unlocked_annotations_idx] <- text_fontface_group
            values$annotations$alpha[unlocked_annotations_idx] <- text_alpha_group
            values$annotations$angle[unlocked_annotations_idx] <- text_angle_group
            values$annotations$fill[unlocked_annotations_idx] <- text_fill_group
          }

          modified_elements <- c(modified_elements, paste(length(unlocked_annotations_idx), "annotations"))

          values$group_storage$group_settings[[group_id]]$last_text_font_group <- text_font_group
          values$group_storage$group_settings[[group_id]]$last_text_size_group <- text_size_group
          values$group_storage$group_settings[[group_id]]$last_text_color_group <- text_color_group
          values$group_storage$group_settings[[group_id]]$last_text_fontface_group <- text_fontface_group
          values$group_storage$group_settings[[group_id]]$last_text_alpha_group <- text_alpha_group
          values$group_storage$group_settings[[group_id]]$last_text_angle_group <- text_angle_group
          values$group_storage$group_settings[[group_id]]$last_apply_text_fill_group <- apply_text_fill_group
          values$group_storage$group_settings[[group_id]]$last_text_fill_group <- text_fill_group
        }
      }

      if (!is.null(values$loops) && nrow(values$loops) > 0) {
        unlocked_loops_idx <- which(!values$loops$locked & values$loops$group == group_id)
        if (length(unlocked_loops_idx) > 0) {

          if (group_shift_xy) {
            values$loops$x_center[unlocked_loops_idx] <- values$loops$x_center[unlocked_loops_idx] + shift_x
            values$loops$y_center[unlocked_loops_idx] <- values$loops$y_center[unlocked_loops_idx] + shift_y
          }

          if (group_aesthetics_loop_only) {
            values$loops$radius[unlocked_loops_idx] <- radius_group
            values$loops$width[unlocked_loops_idx] <- line_width_loop_group
            values$loops$color[unlocked_loops_idx] <- line_color_loop_group
            values$loops$alpha[unlocked_loops_idx] <- line_alpha_loop_group
            values$loops$arrow_type[unlocked_loops_idx] <- arrow_type_loop_group
            values$loops$arrow_size[unlocked_loops_idx] <- arrow_size_loop_group
            values$loops$loop_width[unlocked_loops_idx] <- width_loop_group
            values$loops$loop_height[unlocked_loops_idx] <- height_loop_group
            values$loops$two_way[unlocked_loops_idx] <- two_way_arrow_loop_group
            values$loops$orientation[unlocked_loops_idx] <- orientation_loop_group
            values$loops$locked[unlocked_loops_idx] <- gap_size_loop_group

          }

          modified_elements <- c(modified_elements, paste(length(unlocked_loops_idx), "loops"))

          values$group_storage$group_settings[[group_id]]$last_radius_group <- radius_group
          values$group_storage$group_settings[[group_id]]$last_line_width_loop_group <- line_width_loop_group
          values$group_storage$group_settings[[group_id]]$last_line_color_loop_group <- line_color_loop_group
          values$group_storage$group_settings[[group_id]]$last_line_alpha_loop_group <- line_alpha_loop_group
          values$group_storage$group_settings[[group_id]]$last_arrow_type_loop_group <- arrow_type_loop_group
          values$group_storage$group_settings[[group_id]]$last_arrow_size_loop_group <- arrow_size_loop_group
          values$group_storage$group_settings[[group_id]]$last_width_loop_group <- width_loop_group
          values$group_storage$group_settings[[group_id]]$last_height_loop_group <- height_loop_group
          values$group_storage$group_settings[[group_id]]$last_two_way_arrow_loop_group <- two_way_arrow_loop_group
          values$group_storage$group_settings[[group_id]]$last_orientation_loop_group <- orientation_loop_group
          values$group_storage$group_settings[[group_id]]$last_gap_size_loop_group <- gap_size_loop_group
        }
      }

      if (length(modified_elements) > 0) {
        showNotification(
          HTML(paste("Changes applied in group", "<b>", group_id, "</b>")),
          type = "message"
        )
      } else {
        showNotification(
          HTML(paste("No unlocked elements found in the selected group to apply changes in group", "<b>", group_id, "</b>")),
          type = "warning"
        )
      }

      output$plot <- renderPlot({
        recreate_plot()
      })

    }, error = function(e) {
      showNotification(
        paste("Error applying group changes:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$apply_point_bulk_shift, {
    tryCatch({
      save_state()

      # Filter unlocked points
      unlocked_points <- values$points[!values$points$locked & values$points$group == input$group_select, ]
      unlocked_points_idx <- which(!values$points$locked & values$points$group == input$group_select)

      shift_x <- if (!is.null(input$bulk_shift_x) && input$bulk_shift_x != "" && !is.na(as.numeric(input$bulk_shift_x))) {
        as.numeric(input$bulk_shift_x)
      } else {
        0
      }

      shift_y <- if (!is.null(input$bulk_shift_y) && input$bulk_shift_y != "" && !is.na(as.numeric(input$bulk_shift_y))) {
        as.numeric(input$bulk_shift_y)
      } else {
        0
      }

      if (input$bulk_shift_point_only) {
        if (nrow(unlocked_points) > 0) {
          values$points$x[unlocked_points_idx] <- unlocked_points$x + shift_x
          values$points$y[unlocked_points_idx] <- unlocked_points$y + shift_y
          showNotification("XY shifts applied to unlocked points.", type = "message")
        } else {
          showNotification("No unlocked points found to apply XY shifts.", type = "warning")
        }
      }


      if (input$bulk_aesthetics_point_only) {
        if (nrow(unlocked_points) > 0) {
          values$points$color[unlocked_points_idx] <- input$point_color
          values$points$shape[unlocked_points_idx] <- input$shape
          values$points$size[unlocked_points_idx] <- input$point_size
          values$points$border_width[unlocked_points_idx] <- input$border_width
          values$points$border_color[unlocked_points_idx] <- input$border_color
          values$points$alpha[unlocked_points_idx] <- input$point_alpha
          values$points$orientation[unlocked_points_idx] <- input$point_orientation

          if (input$shape %in% c('rectangle', 'oval', 'diamond')) {
            values$points$width_height_ratio[unlocked_points_idx] <- input$width_height_ratio
          } else {
            values$points$width_height_ratio[unlocked_points_idx] <- 1
          }


          showNotification("Aesthetic changes applied to unlocked points.", type = "message")
        } else {
          showNotification("No unlocked points found to apply aesthetic changes.", type = "warning")
        }
      }


      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error applying bulk shift:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  # Add line
  observeEvent(input$add_line, {
    tryCatch({
      req(input$x_start, input$y_start, input$x_end, input$y_end)
      save_state()

      new_line <- data.frame(
        x_start = as.numeric(input$x_start),
        y_start = as.numeric(input$y_start),
        x_end = as.numeric(input$x_end),
        y_end = as.numeric(input$y_end),
        ctrl_x = if (input$line_type %in% c("Curved Line", "Curved Arrow")) as.numeric(curve_ctrl_values$ctrl_x) else NA,
        ctrl_y = if (input$line_type %in% c("Curved Line", "Curved Arrow")) as.numeric(curve_ctrl_values$ctrl_y) else NA,
        ctrl_x2 = if (input$line_type %in% c("Curved Line", "Curved Arrow")) as.numeric(curve_ctrl_values$ctrl_x2) else NA,
        ctrl_y2 = if (input$line_type %in% c("Curved Line", "Curved Arrow")) as.numeric(curve_ctrl_values$ctrl_y2) else NA,
        curvature_magnitude = if (input$line_type %in% c("Curved Line", "Curved Arrow")) as.numeric(input$curvature_magnitude) else NA,
        rotate_curvature = if (input$line_type %in% c("Curved Line", "Curved Arrow")) as.numeric(input$rotate_curvature) else NA,
        curvature_asymmetry = if (input$line_type %in% c("Curved Line", "Curved Arrow")) as.numeric(input$curvature_asymmetry) else NA,
        type = input$line_type,
        color = input$line_color,
        end_color = if (input$color_type == "Gradient") input$end_color else NA,
        color_type = input$color_type,
        gradient_position = if (input$color_type == "Gradient") input$gradient_position else NA,
        width = input$line_width,
        alpha = input$line_alpha,
        arrow = input$line_type %in% c("Straight Arrow", "Curved Arrow"),
        arrow_type = if (input$line_type %in% c("Straight Arrow", "Curved Arrow")) input$arrow_type else NA,
        arrow_size = if (input$line_type %in% c("Straight Arrow", "Curved Arrow")) input$arrow_size else NA,
        two_way = input$two_way_arrow,
        lavaan = FALSE,
        network = FALSE,
        line_style = input$line_style,
        locked = FALSE,
        group = debounced_which_group(),
        stringsAsFactors = FALSE
      )
      add_new_line(new_line)
      showNotification("Line added successfully.", type = "message", duration = 5)


      output$plot <- renderPlot({
        recreate_plot()
      })
      # values$lines <- rbind(values$lines, new_line)
    }, error = function(e) {
      showNotification(
        paste("Error adding line:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$lock_selected_lines, {
    tryCatch({
      selected_rows <- input$line_table_rows_selected

      if (!is.null(selected_rows) && length(selected_rows) > 0) {
        save_state()
        values$lines$locked[selected_rows] <- TRUE # Lock the selected lines
        showNotification(paste(length(selected_rows), "line(s) locked."), type = "message")
      } else {
        showNotification("No line selected. Please select line(s) to lock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(
        paste("Error locking selected lines:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observeEvent(input$unlock_selected_lines, {
    tryCatch({
      selected_rows <- input$line_table_rows_selected

      if (!is.null(selected_rows) && length(selected_rows) > 0) {
        save_state()
        values$lines$locked[selected_rows] <- FALSE # Unlock the selected lines
        showNotification(paste(length(selected_rows), "line(s) unlocked."), type = "message")
      } else {
        showNotification("No line selected. Please select line(s) to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(
        paste("Error unlocking selected lines:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$finalize_sem_lines, {
    tryCatch({
      selected_rows <- input$line_table_rows_selected

      if (!is.null(selected_rows) && length(selected_rows) > 0) {
        save_state()
        values$lines$lavaan[selected_rows] <- FALSE # Unlock the selected lines
        showNotification(paste(length(selected_rows), "edges(s) unlocked."), type = "message")
      } else {
        showNotification("No SEM edge selected. Please select edges(s) to finalize.", type = "warning")
      }
    }, error = function(e) {
      showNotification(
        paste("Error finalizing selected edges:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$finalize_network_lines, {
    tryCatch({
      selected_rows <- input$line_table_rows_selected

      if (!is.null(selected_rows) && length(selected_rows) > 0) {
        save_state()
        values$lines$network[selected_rows] <- FALSE # Unlock the selected lines
        showNotification(paste(length(selected_rows), "line(s) unlocked."), type = "message")
      } else {
        showNotification("No network edge selected. Please select edge(s) to finalize.", type = "warning")
      }
    }, error = function(e) {
      showNotification(
        paste("Error finalizing selected edges:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observeEvent(input$lock_selected_annotation, {
    tryCatch({
      selected_rows <- input$annotation_table_rows_selected

      if (!is.null(selected_rows) && length(selected_rows) > 0) {
        save_state()
        values$annotations$locked[selected_rows] <- TRUE # Lock the selected annotations
        showNotification(paste(length(selected_rows), "annotation(s) locked."), type = "message")
      } else {
        showNotification("No annotation selected. Please select annotation(s) to lock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(
        paste("Error locking selected annotations:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observeEvent(input$unlock_selected_annotation, {
    tryCatch({
      selected_rows <- input$annotation_table_rows_selected

      if (!is.null(selected_rows) && length(selected_rows) > 0) {
        save_state()
        values$annotations$locked[selected_rows] <- FALSE # Unlock the selected annotations
        showNotification(paste(length(selected_rows), "annotation(s) unlocked."), type = "message")
      } else {
        showNotification("No annotation selected. Please select annotation(s) to unlock.", type = "warning")
      }
    }, error = function(e) {
      showNotification(
        paste("Error unlocking selected annotations:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$finalize_sem_annotations, {
    tryCatch({
      selected_rows <- input$annotation_table_rows_selected

      if (!is.null(selected_rows) && length(selected_rows) > 0) {
        save_state()
        values$annotations$locked[selected_rows] <- FALSE # Unlock the selected annotations
        showNotification(paste(length(selected_rows), "SEM annotation(s) finalized"), type = "message")
      } else {
        showNotification("No SEM annotation selected. Please select annotation(s) to finalize", type = "warning")
      }
    }, error = function(e) {
      showNotification(
        paste("Error finalizing selected annotations:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$finalize_network_annotations, {
    tryCatch({
      selected_rows <- input$annotation_table_rows_selected

      if (!is.null(selected_rows) && length(selected_rows) > 0) {
        save_state()
        values$annotations$locked[selected_rows] <- FALSE # Unlock the selected annotations
        showNotification(paste(length(selected_rows), "Network annotation(s) finalized."), type = "message")
      } else {
        showNotification("No network annotation selected. Please select annotation(s) to finalize.", type = "warning")
      }
    }, error = function(e) {
      showNotification(
        paste("Error finalizing selected annotations:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  # Auto-generate edges
  observeEvent(input$auto_generate_edges_button, {
    tryCatch({
      req(nrow(values$points) > 1)

      save_state()

      connection_type <- input$connection_type
      directed <- input$edge_type == "Arrow"

      new_edges <- auto_generate_edges(
        points_data = values$points,
        layout_type = connection_type,
        line_color = input$auto_line_color,
        line_width = input$auto_line_width,
        line_alpha = input$auto_line_alpha,
        line_style = input$auto_line_style,
        particular_node = input$particular_node,
        auto_endpoint_spacing = input$auto_endpoint_spacing %||% 0,
        random_seed = input$random_seed,
        bezier_auto_edges = input$bezier_auto_edges,
        auto_edges_curvature_magnitude = input$auto_edges_curvature_magnitude,
        auto_edges_rotate_curvature = input$auto_edges_rotate_curvature,
        auto_edges_curvature_asymmetry = input$auto_edges_curvature_asymmetry,
        directed = directed,
        arrow_location = input$arrow_location,
        arrow_type = input$arrow_type,
        arrow_size = input$arrow_size,
        two_way_arrow = input$two_way_arrow,
        which_group = input$group_select
      )

      if (!is.null(new_edges)) {
        values$lines <- rbind(values$lines, new_edges)
      } else {
        showModal(modalDialog(
          title = "Error",
          "Not enough unlocked points to generate edges.",
          easyClose = TRUE
        ))
      }

      showNotification("Successfully generated edges on unlcoed points.", type = "message")


      output$plot <- renderPlot({
        recreate_plot()
      })

    }, error = function(e) {
      showNotification(
        paste("Error auto-generating edges:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$auto_generate_text_button, {
    tryCatch({
      req(nrow(values$points) > 0)

      save_state()

      new_text <- auto_generate_text(
        points_data = values$points,
        text_type = input$text_type_auto,
        text = input$text_auto,
        start_value = input$sequence_start_auto,
        text_color = input$text_color,
        text_fill = if (input$apply_fill_text_color) input$text_fill else NA,
        text_size = input$text_size,
        font = input$text_font,
        alpha = input$text_alpha,
        fontface = input$text_fontface,
        orientation = input$text_angle,
        which_group = input$group_select
      )

      if (!is.null(new_text)) {
        values$annotations <- rbind(values$annotations, new_text)

        showNotification("Text annotations generated successfully.", type = "message")
      } else {
        showNotification("No unlocked points available to generate text annotations.", type = "warning")
      }

    }, error = function(e) {
      showNotification(
        paste("Error generating text annotations:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$auto_generate_loops_button, {
    tryCatch({
      save_state()

      shift_x <- if (!is.null(input$loop_offset_x) && input$loop_offset_x != "" && !is.na(as.numeric(input$loop_offset_x))) {
        as.numeric(input$loop_offset_x)
      } else {
        0
      }

      shift_y <- if (!is.null(input$loop_offset_y) && input$loop_offset_y != "" && !is.na(as.numeric(input$loop_offset_y))) {
        as.numeric(input$loop_offset_y)
      } else {
        0
      }

      new_loops <- auto_generate_loops(
        points_data = values$points,
        loop_radius = input$radius,
        loop_width = input$width_loop,
        loop_height = input$height_loop,
        gap_size = input$gap_size_loop,
        orientation = input$orientation_loop,
        arrow_type = input$arrow_type_loop,
        arrow_size = input$arrow_size_loop,
        two_way = input$two_way_arrow_loop,
        loop_color = input$line_color_loop,
        alpha = input$line_alpha_loop,
        line_width = input$line_width_loop,
        which_group = input$group_select
      )

      if (!is.null(new_loops)) {
        new_loops$x_center <- new_loops$x_center + shift_x
        new_loops$y_center <- new_loops$y_center + shift_y

        values$loops <- rbind(values$loops, new_loops)
        showNotification("Self-loops generated successfully with applied shifts.", type = "message")
      } else {
        showNotification("No unlocked points available to generate self-loops.", type = "warning")
      }

    }, error = function(e) {
      showNotification(
        paste("Error generating self-loops:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observeEvent(input$data_table_cell_edit, {
    tryCatch({
      info <- input$data_table_cell_edit
      save_state()

      # Check the column being edited and update accordingly
      if (info$col %in% c("x", "y", "size", "border_width", "alpha", "width_height_ratio", "orientation")) {
        # Ensure numeric values
        values$points[info$row, info$col] <- as.numeric(info$value)
      } else if (info$col %in% c("color", "border_color")) {
        # Validate hex color code
        if (grepl("^#(?:[0-9a-fA-F]{3}){1,2}$", info$value)) {
          values$points[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification("Invalid color input. Update skipped.", type = "error")
          return() # Skip invalid color updates
        }
      } else if (info$col == "shape") {
        # Safeguard for valid shapes
        valid_shapes <- c("circle", "square", "oval", "triangle", "rectangle", "diamond")
        if (info$value %in% valid_shapes) {
          values$points[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification(
            paste("Invalid shape input. Must be one of:", paste(valid_shapes, collapse = ", ")),
            type = "error"
          )
          return()
        }
      } else if (info$col %in% c("lavaan", "network", "locked")) {
        # Convert logical inputs to uppercase and validate
        logical_value <- toupper(info$value)
        if (logical_value %in% c("TRUE", "FALSE")) {
          values$points[info$row, info$col] <- as.logical(logical_value)
        } else {
          showNotification("Invalid logical input. Must be TRUE or FALSE.", type = "error")
          return() # Skip invalid logical updates
        }
      } else {
        # Default case for other string values
        values$points[info$row, info$col] <- info$value
      }

      # Re-render the plot with updated data

      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error processing table cell edit:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observe({
    tryCatch({
      if (nrow(values$points) > 0) {
        if (!all(is.logical(values$points$locked))) {
          values$points$locked <- as.logical(values$points$locked)
          values$points$locked[is.na(values$points$locked)] <- FALSE
        }

        unlocked_points <- values$points[!values$points$locked, ]
        point_choices <- rownames(unlocked_points)

        updateSelectInput(session, "particular_node", choices = point_choices)
      }
    }, error = function(e) {
    })
  })


  observeEvent(input$apply_line_bulk_shift, {
    tryCatch({
      save_state()

      # Filter unlocked lines
      unlocked_lines <- values$lines[!values$lines$locked & values$lines$group == input$group_select, ]
      unlocked_lines_idx <- which(!values$lines$locked & values$lines$group == input$group_select)

      line_shift_x <- if (!is.null(input$line_bulk_shift_x) && input$line_bulk_shift_x != "" && !is.na(as.numeric(input$line_bulk_shift_x))) {
        as.numeric(input$line_bulk_shift_x)
      } else {
        0
      }

      line_shift_y <- if (!is.null(input$line_bulk_shift_y) && input$line_bulk_shift_y != "" && !is.na(as.numeric(input$line_bulk_shift_y))) {
        as.numeric(input$line_bulk_shift_y)
      } else {
        0
      }


      # curved_lines_idx <- which(unlocked_lines_idx &
      #                             values$lines$type %in% c("Curved Line", "Curved Arrow"))

      curved_lines_idx <- unlocked_lines_idx[values$lines$type[unlocked_lines_idx] %in% c("Curved Line", "Curved Arrow")]


      if (input$bulk_shift_line_only) {
        if (nrow(unlocked_lines) > 0) {
          values$lines$x_start[unlocked_lines_idx] <- unlocked_lines$x_start + line_shift_x
          values$lines$y_start[unlocked_lines_idx] <- unlocked_lines$y_start + line_shift_y
          values$lines$x_end[unlocked_lines_idx] <- unlocked_lines$x_end + line_shift_x
          values$lines$y_end[unlocked_lines_idx] <- unlocked_lines$y_end + line_shift_y

          showNotification("XY shifts applied to unlocked lines.", type = "message")
        } else {
          showNotification("No unlocked lines found to apply XY shifts.", type = "warning")
        }

        if (length(curved_lines_idx) > 0) {
          control_points <- mapply(
            calculate_control_point,
            x_start = values$lines$x_start[curved_lines_idx],
            y_start = values$lines$y_start[curved_lines_idx],
            x_end = values$lines$x_end[curved_lines_idx],
            y_end = values$lines$y_end[curved_lines_idx],
            curvature_magnitude = values$lines$curvature_magnitude[curved_lines_idx],
            rotate_curvature = values$lines$rotate_curvature[curved_lines_idx],
            curvature_asymmetry = values$lines$curvature_asymmetry[curved_lines_idx],
            SIMPLIFY = FALSE
          )


          values$lines$ctrl_x[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_x"), 5)
          values$lines$ctrl_y[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_y"), 5)
          values$lines$ctrl_x2[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_x2"), 5)
          values$lines$ctrl_y2[curved_lines_idx] <- round(sapply(control_points, `[[`, "ctrl_y2"), 5)

        }
      }

      if (input$bulk_aesthetics_line_only) {
        if (any(unlocked_lines_idx)) {
          values$lines$color[unlocked_lines_idx] <- input$line_color
          values$lines$color_type[unlocked_lines_idx] <- input$color_type
          values$lines$width[unlocked_lines_idx] <- input$line_width
          values$lines$alpha[unlocked_lines_idx] <- input$line_alpha
          values$lines$line_style[unlocked_lines_idx] <- input$line_style
          values$lines$type[unlocked_lines_idx] <- input$line_type
          values$lines$two_way[unlocked_lines_idx] <- input$two_way_arrow

          if (input$line_type %in% c("Straight Arrow", "Curved Arrow")) {
            values$lines$arrow_size[unlocked_lines_idx] <- input$arrow_size
            values$lines$arrow_type[unlocked_lines_idx] <- input$arrow_type
          } else {
            values$lines$arrow_size[unlocked_lines_idx] <- NA
            values$lines$arrow_type[unlocked_lines_idx] <- NA
          }

          if (input$color_type == "Gradient") {
            values$lines$end_color[unlocked_lines_idx] <- input$end_color
            values$lines$gradient_position[unlocked_lines_idx] <- input$gradient_position
          }

          showNotification("Aesthetic changes applied to unlocked lines.", type = "message")
        } else {
          showNotification("No unlocked lines found to apply aesthetic changes.", type = "warning")
        }
      }

      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error applying line bulk changes:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  last_saved_state <- reactive({
    if (length(values$undo_stack) > 0) tail(values$undo_stack, 1)[[1]] else NULL
  })


  observeEvent(input$apply_annotation_changes, {
    tryCatch({
      save_state()

      # Check for unlocked annotations
      unlocked_annotations <- which(!values$annotations$locked & values$annotations$group == input$group_select)

      # Safely handle input values for bulk shift
      annotation_shift_x <- if (!is.null(input$annotation_bulk_shift_x) && input$annotation_bulk_shift_x != "" && !is.na(as.numeric(input$annotation_bulk_shift_x))) {
        as.numeric(input$annotation_bulk_shift_x)
      } else {
        0
      }

      annotation_shift_y <- if (!is.null(input$annotation_bulk_shift_y) && input$annotation_bulk_shift_y != "" && !is.na(as.numeric(input$annotation_bulk_shift_y))) {
        as.numeric(input$annotation_bulk_shift_y)
      } else {
        0
      }

      if (input$bulk_shift_annotation_only) {
        if (any(unlocked_annotations)) {
          values$annotations$x[unlocked_annotations] <- values$annotations$x[unlocked_annotations] + annotation_shift_x
          values$annotations$y[unlocked_annotations] <- values$annotations$y[unlocked_annotations] + annotation_shift_y

          showNotification("XY shifts applied to unlocked annotations.", type = "message")
        } else {
          showNotification("No unlocked annotations found to apply XY shifts.", type = "warning")
        }
      }
      if (input$bulk_aesthetics_annotation_only) {
        if (any(unlocked_annotations)) {
          # Update styling attributes for unlocked annotations
          values$annotations$font[unlocked_annotations] <- input$text_font
          values$annotations$size[unlocked_annotations] <- input$text_size
          values$annotations$color[unlocked_annotations] <- input$text_color
          values$annotations$fontface[unlocked_annotations] <- input$text_fontface
          values$annotations$alpha[unlocked_annotations] <- input$text_alpha
          values$annotations$angle[unlocked_annotations] <- input$text_angle
          values$annotations$fill[unlocked_annotations] <- if (input$apply_fill_text_color) input$text_fill else NA

          showNotification("Aesthetic changes applied to unlocked annotations.", type = "message")
        } else {
          showNotification("No unlocked annotations found to apply aesthetic changes.", type = "warning")
        }
      }

      # Trigger a plot update

      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error applying annotation changes:", e$message),
        type = "error",
        duration = 5
      )
    })
  })



  # Add annotation
  observeEvent(input$add_annotation, {
    tryCatch({
      req(input$annotation_text, input$annotation_x, input$annotation_y)
      save_state()

      new_annotation <- data.frame(
        text = input$annotation_text,
        x = as.numeric(input$annotation_x),
        y = as.numeric(input$annotation_y),
        font = input$text_font,
        size = input$text_size,
        color = input$text_color,
        fill =  if (input$apply_fill_text_color) input$text_fill else NA,
        angle = input$text_angle,
        alpha = input$text_alpha,
        fontface = input$text_fontface,
        math_expression = input$math_expression,
        hjust = 0.5,
        vjust = 0.5,
        lavaan = FALSE,
        network = FALSE,
        locked = FALSE,
        group_label = FALSE,
        loop_label = FALSE,
        group = debounced_which_group(),
        stringsAsFactors = FALSE
      )
      values$annotations <- rbind(values$annotations, new_annotation)
      showNotification("Annotation added successfully.", type = "message", duration = 5)


      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error adding annotation:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  # Add self-loop arrow
  observeEvent(input$add_loop, {
    tryCatch({
      req(input$x_center, input$y_center, input$radius, input$gap_size_loop, input$width_loop, input$height_loop, input$orientation_loop)
      save_state()

      t <- seq(0, 2 * pi, length.out = 100)
      gap_angle <- input$gap_size_loop * pi
      loop_start <- t[t < (2 * pi - gap_angle)]

      x_ellipse <- as.numeric(input$x_center) + input$width_loop * input$radius * cos(loop_start)
      y_ellipse <- as.numeric(input$y_center) + input$height_loop * input$radius * sin(loop_start)

      theta <- input$orientation_loop * pi / 180
      x_rotated <- cos(theta) * (x_ellipse - as.numeric(input$x_center)) - sin(theta) * (y_ellipse - as.numeric(input$y_center)) + as.numeric(input$x_center)
      y_rotated <- sin(theta) * (x_ellipse - as.numeric(input$x_center)) + cos(theta) * (y_ellipse - as.numeric(input$y_center)) + as.numeric(input$y_center)

      circle_data <- data.frame(x = x_rotated, y = y_rotated)

      arrow_type <- if (input$arrow_type_loop == "closed") arrow(type = "closed", length = unit(input$arrow_size_loop, "inches")) else arrow(type = "open", length = unit(input$arrow_size_loop, "inches"))

      new_loop <- data.frame(
        x_center = as.numeric(input$x_center),
        y_center = as.numeric(input$y_center),
        radius = input$radius,
        color = input$line_color_loop,
        width = input$line_width_loop,
        alpha = input$line_alpha_loop,
        arrow_type = input$arrow_type_loop,
        arrow_size = input$arrow_size_loop,
        gap_size = input$gap_size_loop,
        loop_width = input$width_loop,
        loop_height = input$height_loop,
        orientation = input$orientation_loop,
        lavaan = FALSE,
        two_way = input$two_way_arrow_loop,
        locked = FALSE,
        group = debounced_which_group(),
        stringsAsFactors = FALSE
      )

      values$loops <- rbind(values$loops, new_loop)

      showNotification("Self-loop arrow added successfully.", type = "message", duration = 5)

      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error adding loop:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$apply_loop_changes, {
    req(nrow(values$loops) > 0)  # Ensure there are loops to modify

    tryCatch({
      save_state()

      unlocked_loops <- which(!values$loops$locked & values$loops$group == input$group_select, )

      shift_x <- if (!is.null(input$loop_bulk_shift_x) && input$loop_bulk_shift_x != "" && !is.na(as.numeric(input$loop_bulk_shift_x))) {
        as.numeric(input$loop_bulk_shift_x)
      } else {
        0
      }

      shift_y <- if (!is.null(input$loop_bulk_shift_y) && input$loop_bulk_shift_y != "" && !is.na(as.numeric(input$loop_bulk_shift_y))) {
        as.numeric(input$loop_bulk_shift_y)
      } else {
        0
      }

      #if (any(unlocked_loops)) {
      if (input$bulk_shift_loop_only) {
        if (any(unlocked_loops)) {
          values$loops$x_center[unlocked_loops] <- values$loops$x_center[unlocked_loops] + shift_x
          values$loops$y_center[unlocked_loops] <- values$loops$y_center[unlocked_loops] + shift_y

          showNotification("XY shifts applied to unlocked loop arrows.", type = "message")
        } else {
          showNotification("No unlocked loops found to apply XY shifts.", type = "warning")
        }
      }


      if (input$bulk_aesthetics_loop_only) {
        if (nrow(unlocked_loops) > 0) {
          values$loops$radius[unlocked_loops] <- input$radius
          values$loops$width[unlocked_loops] <- input$line_width_loop
          values$loops$color[unlocked_loops] <- input$line_color_loop
          values$loops$alpha[unlocked_loops] <- input$line_alpha_loop
          values$loops$arrow_type[unlocked_loops] <- input$arrow_type_loop
          values$loops$arrow_size[unlocked_loops] <- input$arrow_size_loop
          values$loops$loop_width[unlocked_loops] <- input$width_loop
          values$loops$loop_height[unlocked_loops] <- input$height_loop
          values$loops$two_way[unlocked_loops] <- input$two_way_arrow_loop
          values$loops$orientation[unlocked_loops] <- input$orientation_loop
          values$loops$locked[unlocked_loops] <- input$gap_size_loop

          showNotification("Aesthetic changes applied to unlocked loop arrows.", type = "message")
        } else {
          showNotification("No unlocked loop arrows found to apply aesthetic changes.", type = "warning")
        }
      }

    }, error = function(e) {
      showNotification(
        paste("Error applying loop changes:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  last_valid_syntax <- reactiveVal(NULL)

  observeEvent(input$generate_sem, {
    req(input$lavaan_syntax)
    save_state()

    group_id <- as.character(debounced_which_group())

    if (is.null(values$group_storage$modifications[[group_id]])) {
      if (is.null(values$group_storage$modifications)) {
        values$group_storage$modifications <- list()
      }
      values$group_storage$modifications[[group_id]] <- create_mod_defaults(type = "sem")
    }

    if (is.null(values$group_storage$sem[[group_id]])) {
      if (is.null(values$group_storage$sem)) {
        values$group_storage$sem <- list()
      }
      values$group_storage$sem[[group_id]] <- create_group_storage(type = "sem")
    }

    if (!grepl("lavaan_string", input$sem_code) || !grepl("data", input$sem_code)) {
      showNotification(
        "Custom SEM code must include `lavaan_string` and `data` as-is.",
        type = "error"
      )
      return(NULL)
    }

    # updateSelectizeInput(session, "layer_order",
    #                      selected = c("annotations", "loops", "lines", "points"))

    tryCatch({

      group_storage <- values$group_storage$sem[[group_id]]
      lavaan_layout <- input$lavaan_layout

      lavaan_fit <- lavstring_to_fit(input$lavaan_syntax,
                                     sem_code = input$sem_code,
                                     data_file = model_state$data,
                                     multi_group = input$multigroup_data_upload,
                                     group_var = input$group_var,
                                     invariance_level = input$invariance_level,
                                     model_type = input$sem_model_type,
                                     nfactors = if (input$model_type == "efa") input$nfactors else NULL)


      intercepts <- ifelse(input$multigroup_data_upload, input$show_intercepts, TRUE)

      sem_paths <- lavaan_to_sempaths(fit = lavaan_fit,
                                      data_file = model_state$data,
                                      layout_algorithm = ifelse(lavaan_layout == 'layout_ai', 'default', lavaan_layout),
                                      intercepts = intercepts,
                                      annotate_estimates = input$annotate_sem_est,
                                      standardized = input$std_est,
                                      unstandardized = input$ustd_est,
                                      conf_int = input$ci_est,
                                      p_val = input$pval_est,
                                      multi_group = input$multigroup_data_upload,
                                      group_var = input$group_var,
                                      group_level = input$group_level,
                                      p_val_alpha = input$p_val_alpha,
                                      residuals = input$residuals)


      lavaan_layout_matrix <- sem_paths$layout

      node_names <- names(sem_paths$graphAttributes$Nodes$labels)
      if (is.null(node_names)) node_names <- sem_paths$graphAttributes$Nodes$labels

      edges_df0 <- data.frame(
        from = sem_paths$Edgelist$from,
        to = sem_paths$Edgelist$to
      )

      edges_df0 <- edges_df0[!duplicated(
        t(apply(edges_df0[c("from", "to")], 1, sort))
      ), ]

      if (!input$residuals)  edges_df <- edges_df0[edges_df0$from != edges_df0$to, ] else edges_df <- edges_df0

      # Handle intercepts
      intercept_indices <- which(node_names == "1")
      intercept_sources <- character(length(intercept_indices))

      for (i in seq_along(intercept_indices)) {
        intercept_idx <- intercept_indices[i]

        connected_edges <- edges_df[edges_df$from == intercept_idx | edges_df$to == intercept_idx, ]

        if (nrow(connected_edges) > 0) {
          target_nodes <- c(connected_edges$from, connected_edges$to)
          target_nodes <- target_nodes[target_nodes != intercept_idx]

          if (length(target_nodes) > 0) {
            target_var <- node_names[target_nodes[1]]
            intercept_sources[i] <- paste0("Intercept_", target_var)
          } else {
            intercept_sources[i] <- paste0("Intercept_", i)
          }
        } else {
          intercept_sources[i] <- paste0("Intercept_", i)
        }
      }

      node_names[intercept_indices] <- intercept_sources

      edges_from <- node_names[edges_df$from]
      edges_to <- node_names[edges_df$to]

      values$group_storage$sem[[group_id]]$edges <- data.frame(source = edges_from,
                                                               target = edges_to)

      values$group_storage$sem[[group_id]]$nodes <- data.frame(node = node_names)

      node_coords0 <- as.data.frame(sem_paths$layout)
      colnames(node_coords0) <- c("x", "y")
      node_coords0$name <- node_names
      node_coords0$text <- names(sem_paths$graphAttributes$Nodes$labels)

      if (lavaan_layout == 'layout_ai') {
        if (input$ai_model %in% c("gemini", "openai", "mistral", "claude")) {
          api_key <- switch(
            input$ai_model,
            "gemini" = input$gemini_api_key,
            "openai" = input$openai_api_key,
            "mistral" = input$mistral_api_key,
            "claude" = input$claude_api_key
          )

          tryCatch({
            validate_api_key(api_key, input$ai_model)
          }, error = function(e) {
            showNotification(
              paste("API key validation failed for", input$ai_model, ":", e$message),
              type = "error"
            )
            return(NULL)
          })
        }

        ai_settings <- list(
          gemini = input$gemini_api_key,
          openai = input$openai_api_key,
          mistral = input$mistral_api_key,
          claude = input$claude_api_key,
          ollama_model = input$ollama_model
        )

        showModal(modalDialog(
          title = paste("Generating SEM layout with", input$ai_model, "..."),
          "Please wait while AI generates SEM layout",
          footer = NULL,
          easyClose = FALSE
        ))

        prompt <- create_layout_prompt(node_coords0, comments = input$additional_prompts_layout)
        ai_response <- tryCatch({
          call_selected_ai(prompt, input$ai_model, ai_settings)
        }, error = function(e) {
          removeModal()
          showNotification(
            paste("Error calling AI model:", e$message),
            type = "error"
          )
          return(NULL)
        })

        if (!is.null(ai_response)) {
          node_coords0 <- tryCatch({
            parse_ai_layout_response(ai_response)
          }, error = function(e) {
            showNotification(
              paste("Error parsing AI response:", e$message),
              type = "error"
            )
            return(node_coords0) # keep old coords if parsing fails
          })

          removeModal()
          showNotification("Lavaan layout generated successfully with GenAI!", type = "message")
        }
      }

      node_coords <- node_coords0 # template

      node_coords <- generate_sem_node_coords(sem_paths, relative_x_position = input$relative_x_position,
                                              relative_y_position = input$relative_y_position,
                                              center_x = input$center_x_position,
                                              center_y = input$center_y_position,
                                              flip_layout = input$flip_sem_layout,
                                              flip_direction = input$flip_sem_layout_direction,
                                              rotate_layout = input$rotate_sem_layout,
                                              rotate_angle = input$rotate_sem_layout_angle)

      text_color_fill <- if (input$apply_fill_edges_color) input$text_fill_edges else NA

      loop_names_remove <- group_storage$last_loop_names_remove_ui

      graph_data <- generate_graph_from_sempaths(sem_paths, node_coords,
                                                 latent_shape = input$latent_shape,
                                                 observed_shape = input$observed_shape,
                                                 int_shape = input$int_shape,
                                                 point_size_latent = input$latent_size_input,
                                                 point_size_observed = input$observed_size_input,
                                                 point_size_int = input$int_size_input,
                                                 width_height_ratio_latent = input$width_height_ratio_latent,
                                                 width_height_ratio_observed = input$width_height_ratio_observed,
                                                 width_height_ratio_int = input$width_height_ratio_int,
                                                 line_width = input$line_width_input,
                                                 line_alpha = input$line_alpha_input,
                                                 text_size_latent = input$text_size_latent,
                                                 text_font_latent = input$text_font_latent,
                                                 text_color_latent = input$text_color_latent,
                                                 text_alpha_latent = input$text_alpha_latent,
                                                 text_fontface_latent = input$text_fontface_latent,
                                                 text_size_others = input$text_size_others,
                                                 text_font_others = input$text_font_others,
                                                 text_color_others = input$text_color_others,
                                                 text_alpha_others = input$text_alpha_others,
                                                 text_fontface_others = input$text_fontface_others,
                                                 text_size_edges = input$text_size_edges,
                                                 text_font_edges = input$text_font_edges,
                                                 text_color_edges = input$text_color_edges,
                                                 text_color_fill = text_color_fill,
                                                 text_alpha_edges = input$text_alpha_edges,
                                                 text_fontface_edges = input$text_fontface_edges,
                                                 point_color_latent = input$latent_color_input,
                                                 point_color_observed = input$observed_color_input,
                                                 point_color_int = input$int_color_input,
                                                 edge_color = input$edge_color_input,
                                                 line_endpoint_spacing = input$line_endpoint_spacing,
                                                 node_border_color = input$node_border_color,
                                                 node_border_width = input$node_border_width,
                                                 arrow_type = input$lavaan_arrow_type,
                                                 arrow_size = input$lavaan_arrow_size,
                                                 lavaan_arrow_location = input$lavaan_arrow_location,
                                                 zoom_factor = input$zoom,
                                                 lavaan_curvature_magnitude = input$lavaan_curvature_magnitude,
                                                 lavaan_rotate_curvature = input$lavaan_rotate_curvature,
                                                 lavaan_curvature_asymmetry = input$lavaan_curvature_asymmetry,
                                                 lavaan_curved_x_shift = input$lavaan_curved_x_shift,
                                                 lavaan_curved_y_shift = input$lavaan_curved_y_shift,
                                                 remove_edgelabels = input$remove_edgelabels,
                                                 highlight_sig_path = input$highlight_sig_path,
                                                 sig_path_color = input$sig_path_color,
                                                 non_sig_path_color = input$non_sig_path_color,
                                                 sig_label_fontface = input$sig_label_fontface,
                                                 non_sig_label_fontface = input$non_sig_label_fontface,
                                                 residuals = input$residuals,
                                                 residuals_orientation_type = input$residuals_orientation_type,
                                                 lavaan_loop_offset = input$lavaan_loop_offset,
                                                 lavaan_radius = input$lavaan_radius,
                                                 lavaan_line_color_loop = input$lavaan_line_color_loop,
                                                 lavaan_line_alpha_loop = input$lavaan_line_alpha_loop,
                                                 lavaan_arrow_type_loop = input$lavaan_arrow_type_loop,
                                                 lavaan_arrow_size_loop = input$lavaan_arrow_size_loop,
                                                 lavaan_width_loop = input$lavaan_width_loop,
                                                 lavaan_height_loop = input$lavaan_height_loop,
                                                 lavaan_gap_size_loop = input$lavaan_gap_size_loop,
                                                 lavaan_two_way_arrow_loop = input$lavaan_two_way_arrow_loop,
                                                 data_file = is_there_data$file,
                                                 loop_names_remove = loop_names_remove,
                                                 which_group = debounced_which_group()
      )


      # Nodes Coords
      if (lavaan_layout == 'layout_ai') values$group_storage$sem[[group_id]]$node_coords0 <- node_coords0 # keep the layout for AI

      values$group_storage$sem[[group_id]]$data_file <- is_there_data$file
      values$group_storage$sem[[group_id]]$intercepts <- input$show_intercepts

      # Node shapes and sizes
      values$group_storage$sem[[group_id]]$last_latent_shape <- input$latent_shape
      values$group_storage$sem[[group_id]]$last_observed_shape <- input$observed_shape
      values$group_storage$sem[[group_id]]$last_int_shape <- input$int_shape
      values$group_storage$sem[[group_id]]$last_point_size_latent <- input$latent_size_input
      values$group_storage$sem[[group_id]]$last_point_size_observed <- input$observed_size_input
      values$group_storage$sem[[group_id]]$last_point_size_int <- input$int_size_input
      values$group_storage$sem[[group_id]]$last_width_height_ratio_latent <- input$width_height_ratio_latent
      values$group_storage$sem[[group_id]]$last_width_height_ratio_observed <- input$width_height_ratio_observed
      values$group_storage$sem[[group_id]]$last_width_height_ratio_int <- input$width_height_ratio_int

      # Line properties
      values$group_storage$sem[[group_id]]$last_line_width <- input$line_width_input
      values$group_storage$sem[[group_id]]$last_line_alpha <- input$line_alpha_input
      values$group_storage$sem[[group_id]]$last_line_endpoint_spacing <- input$line_endpoint_spacing
      values$group_storage$sem[[group_id]]$last_edge_color <- input$edge_color_input
      values$group_storage$sem[[group_id]]$last_lavaan_arrow_location <- input$lavaan_arrow_location

      # Text properties - Latent
      values$group_storage$sem[[group_id]]$last_text_size_latent <- input$text_size_latent
      values$group_storage$sem[[group_id]]$last_text_font_latent <- input$text_font_latent
      values$group_storage$sem[[group_id]]$last_text_color_latent <- input$text_color_latent
      values$group_storage$sem[[group_id]]$last_text_alpha_latent <- input$text_alpha_latent
      values$group_storage$sem[[group_id]]$last_text_fontface_latent <- input$text_fontface_latent

      # Text properties - Others (Observed/Intercept)
      values$group_storage$sem[[group_id]]$last_text_size_others <- input$text_size_others
      values$group_storage$sem[[group_id]]$last_text_font_others <- input$text_font_others
      values$group_storage$sem[[group_id]]$last_text_color_others <- input$text_color_others
      values$group_storage$sem[[group_id]]$last_text_alpha_others <- input$text_alpha_others
      values$group_storage$sem[[group_id]]$last_text_fontface_others <- input$text_fontface_others

      # Text properties - Edges
      values$group_storage$sem[[group_id]]$last_text_size_edges <- input$text_size_edges
      values$group_storage$sem[[group_id]]$last_text_font_edges <- input$text_font_edges
      values$group_storage$sem[[group_id]]$last_text_color_edges <- input$text_color_edges
      values$group_storage$sem[[group_id]]$last_text_color_fill <- text_color_fill
      values$group_storage$sem[[group_id]]$last_apply_text_color_fill <- input$apply_fill_edges_color
      values$group_storage$sem[[group_id]]$last_text_alpha_edges <- input$text_alpha_edges
      values$group_storage$sem[[group_id]]$last_text_fontface_edges <- input$text_fontface_edges

      # Point colors
      values$group_storage$sem[[group_id]]$last_point_color_latent <- input$latent_color_input
      values$group_storage$sem[[group_id]]$last_point_color_observed <- input$observed_color_input
      values$group_storage$sem[[group_id]]$last_point_color_int <- input$int_color_input

      # Node border properties
      values$group_storage$sem[[group_id]]$last_node_border_color <- input$node_border_color
      values$group_storage$sem[[group_id]]$last_node_border_width <- input$node_border_width

      # General font and arrow properties
      # values$group_storage$sem[[group_id]]$last_fontface <- input$fontface
      values$group_storage$sem[[group_id]]$last_arrow_type <- input$lavaan_arrow_type
      values$group_storage$sem[[group_id]]$last_arrow_size <- input$lavaan_arrow_size

      # Others
      values$group_storage$sem[[group_id]]$last_lavaan_syntax <- input$lavaan_syntax
      values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix <- lavaan_layout_matrix
      values$group_storage$sem[[group_id]]$last_sem_code <- input$sem_code
      values$group_storage$sem[[group_id]]$data <- model_state$data
      values$group_storage$sem[[group_id]]$data_file <- is_there_data$file
      values$group_storage$sem[[group_id]]$last_custom_sem <- input$write_sem_code
      values$group_storage$sem[[group_id]]$original <- lavaan_fit
      values$group_storage$sem[[group_id]]$current <- lavaan_fit

      values$group_storage$sem[[group_id]]$last_group_var <- input$group_var
      values$group_storage$sem[[group_id]]$last_group_level <- input$group_level
      values$group_storage$sem[[group_id]]$last_invariance_level <- input$invariance_level
      values$group_storage$sem[[group_id]]$last_annotate_sem_est <- input$annotate_sem_est
      values$group_storage$sem[[group_id]]$last_std_est <- input$std_est
      values$group_storage$sem[[group_id]]$last_ustd_est <- input$ustd_est
      values$group_storage$sem[[group_id]]$last_conf_int <- input$ci_est
      values$group_storage$sem[[group_id]]$last_p_val <- input$pval_est
      values$group_storage$sem[[group_id]]$last_highlight_sig_path <- input$highlight_sig_path


      # values$group_storage$sem[[group_id]]$last_multigroup_data_upload <- input$multigroup_data_upload

      values$group_storage$sem[[group_id]]$last_lavaan_layout <- lavaan_layout

      values$group_storage$sem[[group_id]]$last_lavaan_curved_x_shift <- input$lavaan_curved_x_shift
      values$group_storage$sem[[group_id]]$last_lavaan_curved_y_shift <- input$lavaan_curved_y_shift
      values$group_storage$sem[[group_id]]$last_remove_edgelabels <- input$remove_edgelabels
      values$group_storage$sem[[group_id]]$last_lavaan_rotate_curvature <- input$lavaan_rotate_curvature
      values$group_storage$sem[[group_id]]$last_lavaan_curvature_magnitude <- input$lavaan_curvature_magnitude
      values$group_storage$sem[[group_id]]$last_lavaan_curvature_asymmetry <- input$lavaan_curvature_asymmetry
      values$group_storage$sem[[group_id]]$last_relative_x_position <- input$relative_x_position
      values$group_storage$sem[[group_id]]$last_relative_y_position <- input$relative_y_position
      values$group_storage$sem[[group_id]]$last_center_x_position <- input$center_x_position
      values$group_storage$sem[[group_id]]$last_center_y_position <- input$center_y_position
      values$group_storage$sem[[group_id]]$last_flip_sem_layout <- input$flip_sem_layout
      values$group_storage$sem[[group_id]]$last_flip_sem_layout_direction <- input$flip_sem_layout_direction

      values$group_storage$sem[[group_id]]$last_sig_label_fontface <- input$sig_label_fontface
      values$group_storage$sem[[group_id]]$last_non_sig_label_fontface <- input$non_sig_label_fontface
      values$group_storage$sem[[group_id]]$last_sig_path_color <- input$sig_path_color
      values$group_storage$sem[[group_id]]$last_non_sig_path_color <- input$non_sig_path_color

      values$group_storage$sem[[group_id]]$last_sem_paths <- sem_paths

      values$group_storage$sem[[group_id]]$last_annotate_sem_est <- input$annotate_sem_est
      values$group_storage$sem[[group_id]]$last_multigroup_data_upload <- input$multigroup_data_upload
      values$group_storage$sem[[group_id]]$last_group_var <- input$group_var
      values$group_storage$sem[[group_id]]$last_group_level <- input$group_level
      values$group_storage$sem[[group_id]]$last_p_val_alpha <- input$p_val_alpha

      # loop
      values$group_storage$sem[[group_id]]$last_residuals <- input$residuals
      values$group_storage$sem[[group_id]]$last_lavaan_radius <- input$lavaan_radius
      values$group_storage$sem[[group_id]]$last_lavaan_width_loop <- input$lavaan_width_loop
      values$group_storage$sem[[group_id]]$last_lavaan_line_color_loop <- input$lavaan_line_color_loop
      values$group_storage$sem[[group_id]]$last_lavaan_line_alpha_loop <- input$lavaan_line_alpha_loop
      values$group_storage$sem[[group_id]]$last_lavaan_arrow_type_loop <- input$lavaan_arrow_type_loop
      values$group_storage$sem[[group_id]]$last_lavaan_arrow_size_loop <- input$lavaan_arrow_size_loop
      values$group_storage$sem[[group_id]]$last_lavaan_gap_size_loop <- input$lavaan_gap_size_loop
      values$group_storage$sem[[group_id]]$last_lavaan_height_loop <- input$lavaan_height_loop
      values$group_storage$sem[[group_id]]$last_lavaan_two_way_arrow_loop <- input$lavaan_two_way_arrow_loop
      values$group_storage$sem[[group_id]]$last_lavaan_loop_offset <- input$lavaan_loop_offset
      values$group_storage$sem[[group_id]]$last_residuals_orientation_type <- input$residuals_orientation_type
      values$group_storage$sem[[group_id]]$last_loop_names_remove_hi <- loop_names_remove


      #last_valid_syntax(input$lavaan_syntax)
      values$points <- rbind(values$points, graph_data$points)
      values$lines <- rbind(values$lines, graph_data$lines)
      values$annotations <- rbind(values$annotations, graph_data$annotations)
      values$loops <- rbind(values$loops, graph_data$loops)

      # Show notification
      if (!is.null(sem_file_data())) {
        showNotification("Graph generated successfully using the uploaded CSV file.", type = "message")
      } else {
        showNotification("Graph generated using no data (no edge labels).", type = "message")
      }


      output$plot <- renderPlot({
        recreate_plot()
      })

      update_group(1)

    },
    error = function(e) {
      showNotification(paste("Error in generating SEM from lavaan model:", e$message), type = "error")
    }
    )
  })

  observeEvent(input$multi_group_sem_combine, {
    req(input$group_select)

    tryCatch({
      save_state()

      multi_group_first <- input$multi_group_first_combine
      multi_group_second <- input$multi_group_second_combine

      model_first <- values$group_storage$sem[[multi_group_first]]$current
      model_second <- values$group_storage$sem[[multi_group_second]]$current

      first_group_level <-  values$group_storage$sem[[multi_group_first]]$last_group_level
      second_group_level <- values$group_storage$sem[[multi_group_second]]$last_group_level

      model_same <- identical(lavaan::parTable(model_first),
                              lavaan::parTable(model_second))

      vars_first <- get_vars_from_lavaan(model_first)
      vars_second <- get_vars_from_lavaan(model_second)
      node_same <- identical(vars_first, vars_second)

      first_bundleObject <- values$group_storage$sem[[multi_group_first]]$bundleObject
      second_bundleObject <- values$group_storage$sem[[multi_group_second]]$bundleObject

      is_both_sem_graph <- inherits(first_bundleObject, "sem_graph") && inherits(second_bundleObject, "sem_graph")

      sep_by = input$sep_by

      group_id <- paste(as.character(multi_group_first), sep_by, as.character(multi_group_second))

      if (is.null(values$group_storage$modifications[[group_id]])) {
        if (is.null(values$group_storage$modifications)) {
          values$group_storage$modifications <- list()
        }
        values$group_storage$modifications[[group_id]] <- create_mod_defaults(type = "sem")
      }

      if (is.null(values$group_storage$sem[[group_id]])) {
        if (is.null(values$group_storage$sem)) {
          values$group_storage$sem <- list()
        }
        values$group_storage$sem[[group_id]] <- create_group_storage(type = "sem")
      }

      if (!is.null(values$group_storage$sem[[multi_group_first]]$last_invariance_level) &&
          !is.null(values$group_storage$sem[[multi_group_second]]$last_invariance_level)) {

        if (values$group_storage$sem[[multi_group_first]]$last_invariance_level !=
            values$group_storage$sem[[multi_group_second]]$last_invariance_level) {
          invariance_same <- FALSE
        } else {
          invariance_same <- TRUE
        }
      } else {
        invariance_same <- TRUE
      }

      has_lavaan_first <- "lavaan" %in% is(model_first)[[1]]
      has_blavaan_first <- "blavaan" %in% is(model_first)[[1]]
      has_lavaan_second <- "lavaan" %in% is(model_second)[[1]]
      has_blavaan_second <- "blavaan" %in% is(model_second)[[1]]

      blavaan_half <- (has_lavaan_first && has_blavaan_second) ||
        (has_blavaan_first && has_lavaan_second)


      if (blavaan_half) {
        showNotification("Cannot combine: one model is blavaan, while another is lavaan",
                         type = "warning", duration = 5)
        return()
      }

      if (!invariance_same) {
        showNotification("Cannot combine: models have different invariance levels",
                         type = "warning", duration = 5)
        return()
      }

      if (!is_both_sem_graph) {
        if (model_same) {
          lavaan_layout <- input$lavaan_layout
          lavaan_layout_matrix <- values$group_storage$sem[[multi_group_first]]$last_lavaan_layout_matrix

          intercepts <- ifelse(input$multigroup_data_upload, input$show_intercepts, TRUE)

          lavaan_fit <- model_first

          layout_algorithm <-
            if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix)) {
              lavaan_layout_matrix
            } else if (lavaan_layout == 'layout_ai') {
              "default"
            } else {
              lavaan_layout
            }
          if (is(model_first)[[1]] == "lavaan") {

            sem_paths <- lavaan_to_sempaths(fit = lavaan_fit,
                                            layout_algorithm = layout_algorithm,
                                            intercepts = TRUE,
                                            annotate_estimates = input$annotate_sem_est,
                                            standardized = input$std_est,
                                            unstandardized = input$ustd_est,
                                            conf_int = input$ci_est,
                                            p_val = input$pval_est,
                                            combine = TRUE,
                                            group1 = first_group_level,
                                            group2 = second_group_level,
                                            sep_by = sep_by,
                                            residuals = input$residuals)

          } else if (is(model_first)[[1]] == "blavaan") {
            sem_paths <- blavaan_to_sempaths(fit = lavaan_fit,
                                             layout_algorithm = layout_algorithm,
                                             intercepts = TRUE,
                                             annotate_estimates = input$annotate_sem_est,
                                             standardized = input$std_est,
                                             unstandardized = input$ustd_est,
                                             p_val = input$pval_est,
                                             conf_int = input$ci_est,
                                             combine = TRUE,
                                             group1 = first_group_level,
                                             group2 = second_group_level,
                                             sep_by = sep_by,
                                             residuals = input$residuals)
          }
          values$group_storage$sem[[group_id]]$multi_combine_real <- TRUE

        } else if (node_same) {
          lavaan_layout <- input$lavaan_layout
          lavaan_layout_matrix <- values$group_storage$sem[[multi_group_first]]$last_lavaan_layout_matrix

          intercepts <- TRUE

          layout_algorithm <-
            if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix)) {
              lavaan_layout_matrix
            } else if (lavaan_layout == 'layout_ai') {
              "default"
            } else {
              lavaan_layout
            }

          if (is(model_first)[[1]] == "lavaan") {

            sem_paths <- lavaan_to_sempaths(fit = list(model_first, model_second),
                                            layout_algorithm = layout_algorithm,
                                            intercepts = TRUE,
                                            annotate_estimates = input$annotate_sem_est,
                                            standardized = input$std_est,
                                            unstandardized = input$ustd_est,
                                            conf_int = input$ci_est,
                                            p_val = input$pval_est,
                                            combine = TRUE,
                                            group1 = first_group_level,
                                            group2 = second_group_level,
                                            sep_by = sep_by,
                                            residuals = input$residuals)

          } else if (is(model_first)[[1]] == "MxRAMModel") {

            sem_paths <- lavaan_to_sempaths(fit = list(model_first, model_second),
                                            layout_algorithm = layout_algorithm,
                                            intercepts = TRUE,
                                            annotate_estimates = input$annotate_sem_est,
                                            standardized = input$std_est,
                                            unstandardized = input$ustd_est,
                                            conf_int = input$ci_est,
                                            p_val = input$pval_est,
                                            combine = FALSE,
                                            group1 = first_group_level,
                                            group2 = second_group_level,
                                            sep_by = sep_by,
                                            residuals = input$residuals)

          } else if (is(model_first)[[1]] == "blavaan") {
            sem_paths <- blavaan_to_sempaths(fit = list(model_first, model_second),
                                             layout_algorithm = layout_algorithm,
                                             intercepts = TRUE,
                                             annotate_estimates = input$annotate_sem_est,
                                             standardized = input$std_est,
                                             unstandardized = input$ustd_est,
                                             p_val = input$pval_est,
                                             conf_int = input$ci_est,
                                             combine = FALSE,
                                             group1 = first_group_level,
                                             group2 = second_group_level,
                                             sep_by = sep_by,
                                             residuals = input$residuals)

          }

          values$group_storage$sem[[group_id]]$multi_combine_real <- FALSE
        }

        values$group_storage$sem[[group_id]]$multi_group_sem_combine_menu <- input$multi_group_sem_combine
        # carry over bundle for reference
        values$group_storage$sem[[group_id]]$bundleModelObject <-  values$group_storage$sem[[multi_group_first]]$bundleModelObject
        values$group_storage$sem[[group_id]]$bundleObject <-  values$group_storage$sem[[multi_group_first]]$bundleObject
        values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix <- lavaan_layout_matrix

        values$group_storage$sem[[group_id]]$last_lavaan_layout <- lavaan_layout
        values$group_storage$sem[[group_id]]$last_sep_by <- sep_by

        values$group_storage$sem[[group_id]]$first_object <-  values$group_storage$sem[[multi_group_first]]$bundleObject
        values$group_storage$sem[[group_id]]$second_object <-  values$group_storage$sem[[multi_group_second]]$bundleObject

        node_names <- names(sem_paths$graphAttributes$Nodes$labels)
        if (is.null(node_names)) node_names <- sem_paths$graphAttributes$Nodes$labels
        edges_df0 <- data.frame(
          from = sem_paths$Edgelist$from,
          to = sem_paths$Edgelist$to
        )

        edges_df <- edges_df0[!duplicated(
          t(apply(edges_df0[c("from", "to")], 1, sort))
        ), ]

        # Handle intercepts
        intercept_indices <- which(node_names == "1")
        intercept_sources <- character(length(intercept_indices))

        for (i in seq_along(intercept_indices)) {
          intercept_idx <- intercept_indices[i]

          connected_edges <- edges_df[edges_df$from == intercept_idx | edges_df$to == intercept_idx, ]

          if (nrow(connected_edges) > 0) {
            target_nodes <- c(connected_edges$from, connected_edges$to)
            target_nodes <- target_nodes[target_nodes != intercept_idx]

            if (length(target_nodes) > 0) {
              target_var <- node_names[target_nodes[1]]
              intercept_sources[i] <- paste0("Intercept_", target_var)
            } else {
              intercept_sources[i] <- paste0("Intercept_", i)
            }
          } else {
            intercept_sources[i] <- paste0("Intercept_", i)
          }
        }

        node_names[intercept_indices] <- intercept_sources

        edges_from <- node_names[edges_df$from]
        edges_to <- node_names[edges_df$to]

        values$group_storage$sem[[group_id]]$edges <- data.frame(source = edges_from,
                                                                 target = edges_to)

        values$group_storage$sem[[group_id]]$nodes <- data.frame(node = node_names)

        node_coords0 <- as.data.frame(sem_paths$layout)
        colnames(node_coords0) <- c("x", "y")
        node_coords0$name <- node_names
        node_coords0$text <- names(sem_paths$graphAttributes$Nodes$labels)


        if (lavaan_layout == 'layout_ai') {
          if (input$ai_model %in% c("gemini", "openai", "mistral", "claude")) {
            api_key <- switch(
              input$ai_model,
              "gemini" = input$gemini_api_key,
              "openai" = input$openai_api_key,
              "mistral" = input$mistral_api_key,
              "claude" = input$claude_api_key
            )

            tryCatch({
              validate_api_key(api_key, input$ai_model)
            }, error = function(e) {
              showNotification(
                paste("API key validation failed for", input$ai_model, ":", e$message),
                type = "error"
              )
              return(NULL)
            })
          }

          ai_settings <- list(
            gemini = input$gemini_api_key,
            openai = input$openai_api_key,
            mistral = input$mistral_api_key,
            claude = input$claude_api_key,
            ollama_model = input$ollama_model
          )

          showModal(modalDialog(
            title = paste("Generating SEM layout with", input$ai_model, "..."),
            "Please wait while AI generates SEM layout",
            footer = NULL,
            easyClose = FALSE
          ))

          prompt <- create_layout_prompt(node_coords0, comments = input$additional_prompts_layout)
          ai_response <- tryCatch({
            call_selected_ai(prompt, input$ai_model, ai_settings)
          }, error = function(e) {
            removeModal()
            showNotification(
              paste("Error calling AI model:", e$message),
              type = "error"
            )
            return(NULL)
          })

          if (!is.null(ai_response)) {
            node_coords0 <- tryCatch({
              parse_ai_layout_response(ai_response)
            }, error = function(e) {
              showNotification(
                paste("Error parsing AI response:", e$message),
                type = "error"
              )
              return(node_coords0) # keep old coords if parsing fails
            })

            removeModal()
            showNotification("Lavaan layout generated successfully with GenAI!", type = "message")
          }
        }

        node_coords <- node_coords0 # template

        node_coords$x <- (node_coords$x - mean(range(node_coords$x))) * input$relative_x_position + input$center_x_position
        node_coords$y <- (node_coords$y - mean(range(node_coords$y))) * input$relative_y_position + input$center_y_position

        text_color_fill <- if (input$apply_fill_edges_color) input$text_fill_edges else NA


        graph_data <- generate_graph_from_sempaths(sem_paths, node_coords,
                                                   latent_shape = input$latent_shape,
                                                   observed_shape = input$observed_shape,
                                                   int_shape = input$int_shape,
                                                   point_size_latent = input$latent_size_input,
                                                   point_size_observed = input$observed_size_input,
                                                   point_size_int = input$int_size_input,
                                                   width_height_ratio_latent = input$width_height_ratio_latent,
                                                   width_height_ratio_observed = input$width_height_ratio_observed,
                                                   width_height_ratio_int = input$width_height_ratio_int,
                                                   line_width = input$line_width_input,
                                                   line_alpha = input$line_alpha_input,
                                                   text_size_latent = input$text_size_latent,
                                                   text_font_latent = input$text_font_latent,
                                                   text_color_latent = input$text_color_latent,
                                                   text_alpha_latent = input$text_alpha_latent,
                                                   text_fontface_latent = input$text_fontface_latent,
                                                   text_size_others = input$text_size_others,
                                                   text_font_others = input$text_font_others,
                                                   text_color_others = input$text_color_others,
                                                   text_alpha_others = input$text_alpha_others,
                                                   text_fontface_others = input$text_fontface_others,
                                                   text_size_edges = input$text_size_edges,
                                                   text_font_edges = input$text_font_edges,
                                                   text_color_edges = input$text_color_edges,
                                                   text_color_fill = text_color_fill,
                                                   text_alpha_edges = input$text_alpha_edges,
                                                   text_fontface_edges = input$text_fontface_edges,
                                                   point_color_latent = input$latent_color_input,
                                                   point_color_observed = input$observed_color_input,
                                                   point_color_int = input$int_color_input,
                                                   edge_color = input$edge_color_input,
                                                   line_endpoint_spacing = input$line_endpoint_spacing,
                                                   node_border_color = input$node_border_color,
                                                   node_border_width = input$node_border_width,
                                                   arrow_type = input$lavaan_arrow_type,
                                                   arrow_size = input$lavaan_arrow_size,
                                                   lavaan_arrow_location = input$lavaan_arrow_location,
                                                   zoom_factor = input$zoom,
                                                   lavaan_curvature_magnitude = input$lavaan_curvature_magnitude,
                                                   lavaan_rotate_curvature = input$lavaan_rotate_curvature,
                                                   lavaan_curvature_asymmetry = input$lavaan_curvature_asymmetry,
                                                   lavaan_curved_x_shift = input$lavaan_curved_x_shift,
                                                   lavaan_curved_y_shift = input$lavaan_curved_y_shift,
                                                   remove_edgelabels = input$remove_edgelabels,
                                                   highlight_sig_path = input$highlight_sig_path,
                                                   sig_path_color = input$sig_path_color,
                                                   non_sig_path_color = input$non_sig_path_color,
                                                   sig_label_fontface = input$sig_label_fontface,
                                                   non_sig_label_fontface = input$non_sig_label_fontface,
                                                   residuals = input$residuals,
                                                   residuals_orientation_type = input$residuals_orientation_type,
                                                   lavaan_loop_offset = input$lavaan_loop_offset,
                                                   lavaan_radius = input$lavaan_radius,
                                                   lavaan_line_color_loop = input$lavaan_line_color_loop,
                                                   lavaan_line_alpha_loop = input$lavaan_line_alpha_loop,
                                                   lavaan_arrow_type_loop = input$lavaan_arrow_type_loop,
                                                   lavaan_arrow_size_loop = input$lavaan_arrow_size_loop,
                                                   lavaan_width_loop = input$lavaan_width_loop,
                                                   lavaan_height_loop = input$lavaan_height_loop,
                                                   lavaan_gap_size_loop = input$lavaan_gap_size_loop,
                                                   lavaan_two_way_arrow_loop = input$lavaan_two_way_arrow_loop,
                                                   data_file = TRUE,
                                                   which_group = group_id
        )

        if (lavaan_layout == 'layout_ai') values$group_storage$sem[[group_id]]$node_coords0 <- node_coords0 # keep the layout for AI
        values$group_storage$sem[[group_id]]$last_lavaan_layout <- lavaan_layout

      } else {
        same_object <- identical(first_bundleObject, second_bundleObject)
        if (model_same) {
          if (same_object) {

            if (is(model_first)[[1]] == "lavaan") {
              sem_paths <- combine_tidysem_groups(first_bundleObject, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                  standardized = input$std_est, unstandardized = input$ustd_est, p_val = input$pval_est, conf_int = input$ci_est)
              fit_delta <- combine_tidysem_groups(first_bundleObject, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                  standardized = input$std_est, unstandardized = input$ustd_est, p_val = input$pval_est, conf_int = input$ci_est)
            } else if (is(model_first)[[1]] == "blavaan") {
              sem_paths <- combine_tidysem_groups_bayes(first_bundleObject, model_first, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                        standardized = input$std_est, unstandardized = input$ustd_est, p_val = input$pval_est, conf_int = input$ci_est)
              fit_delta <- combine_tidysem_groups_bayes(first_bundleObject, model_first, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                        standardized = input$std_est, unstandardized = input$ustd_est, p_val = input$pval_est, conf_int = input$ci_est)
            }

            values$group_storage$sem[[group_id]]$multi_combine_real <- TRUE
          }
        } else if (node_same) { # model_same = FALSE

          if (is(model_first)[[1]] == "lavaan") {
            sem_paths <- combine_tidysem_objects(first_bundleObject, second_bundleObject, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                 standardized = input$std_est, unstandardized = input$ustd_est, p_val = input$pval_est, conf_int = input$ci_est)
            fit_delta <- combine_tidysem_objects(first_bundleObject, second_bundleObject, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                 standardized = input$std_est, unstandardized = input$ustd_est, p_val = input$pval_est, conf_int = input$ci_est)

          } else if (is(model_first)[[1]] == "blavaan") {
            sem_paths <- combine_tidysem_objects_bayes(first_bundleObject, second_bundleObject, model_first, model_second, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                       standardized = input$std_est, unstandardized = input$ustd_est, p_val = input$pval_est, conf_int = input$ci_est)
            fit_delta <- combine_tidysem_objects_bayes(first_bundleObject, second_bundleObject, model_first, model_second, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                       standardized = input$std_est, unstandardized = input$ustd_est, p_val = input$pval_est, conf_int = input$ci_est)
          }

          values$group_storage$sem[[group_id]]$multi_combine_real <- FALSE
        }

        text_color_fill <- if (input$apply_fill_edges_color) input$text_fill_edges else NA

        node_names <- sem_paths$nodes$name
        node_types <- sem_paths$nodes$shape

        edges_df0 <- data.frame(
          from = sem_paths$edges$from,
          to = sem_paths$edges$to,
          directed = sem_paths$edges$arrow == "last", # directed
          bidirectional = sem_paths$edges$arrow == "none", # covariance
          labels = sem_paths$edges$label
        )
        edges_df <- edges_df0[!duplicated(
          t(apply(edges_df0[c("from", "to")], 1, sort))
        ), ]

        edges_df <- edges_df[edges_df$from != edges_df$to, ]
        edges_from <- edges_df$from
        edges_to <- edges_df$to


        graph_data <- generate_graph_from_tidySEM(sem_paths, fit_delta,
                                                  relative_x_position = input$relative_x_position,
                                                  relative_y_position = input$relative_y_position,
                                                  center_x = input$center_x_position,
                                                  center_y = input$center_y_position,
                                                  latent_shape = input$latent_shape,
                                                  observed_shape = input$observed_shape,
                                                  int_shape = input$int_shape,
                                                  point_size_latent = input$latent_size_input,
                                                  point_size_observed = input$observed_size_input,
                                                  point_size_int = input$int_size_input,
                                                  width_height_ratio_latent = input$width_height_ratio_latent,
                                                  width_height_ratio_observed = input$width_height_ratio_observed,
                                                  width_height_ratio_int = input$width_height_ratio_int,
                                                  line_width = input$line_width,
                                                  line_alpha = input$line_alpha,
                                                  text_size_latent = input$text_size_latent,
                                                  text_font_latent = input$text_font_latent,
                                                  text_color_latent = input$text_color_latent,
                                                  text_alpha_latent = input$text_alpha_latent,
                                                  text_fontface_latent = input$text_fontface_latent,
                                                  text_size_others = input$text_size_others,
                                                  text_font_others = input$text_font_others,
                                                  text_color_others = input$text_color_others,
                                                  text_alpha_others = input$text_alpha_others,
                                                  text_fontface_others = input$text_fontface_others,
                                                  text_size_edges = input$text_size_edges,
                                                  text_font_edges = input$text_font_edges,
                                                  text_color_edges = input$text_color_edges,
                                                  text_color_fill = text_color_fill,
                                                  text_alpha_edges = input$text_alpha_edges,
                                                  text_fontface_edges = input$text_fontface_edges,
                                                  point_color_latent = input$latent_color_input,
                                                  point_color_observed = input$observed_color_input,
                                                  point_color_int = input$int_color_input,
                                                  edge_color = input$edge_color_input,
                                                  line_endpoint_spacing = input$line_endpoint_spacing,
                                                  node_border_color = input$node_border_color,
                                                  node_border_width = input$node_border_width,
                                                  arrow_type = input$lavaan_arrow_type,
                                                  arrow_size = input$lavaan_arrow_size,
                                                  lavaan_arrow_location = input$lavaan_arrow_location,
                                                  zoom_factor = input$zoom,
                                                  lavaan_curvature_magnitude = input$lavaan_curvature_magnitude,
                                                  lavaan_rotate_curvature = input$lavaan_rotate_curvature,
                                                  lavaan_curvature_asymmetry = input$lavaan_curvature_asymmetry,
                                                  lavaan_curved_x_shift = input$lavaan_curved_x_shift,
                                                  lavaan_curved_y_shift = input$lavaan_curved_y_shift,
                                                  remove_edgelabels = input$remove_edgelabels,
                                                  highlight_sig_path = input$highlight_sig_path,
                                                  sig_path_color = input$sig_path_color,
                                                  non_sig_path_color = input$non_sig_path_color,
                                                  sig_label_fontface = input$sig_label_fontface,
                                                  non_sig_label_fontface = input$non_sig_label_fontface,
                                                  highlight_multi_group = input$highlight_multi_group_edges,
                                                  sig_diff_edge = input$sig_diff_edge,
                                                  sig_diff_edgelabel = input$sig_diff_edgelabel,
                                                  residuals = input$residuals,
                                                  residuals_orientation_type = input$residuals_orientation_type,
                                                  lavaan_loop_offset = input$lavaan_loop_offset,
                                                  lavaan_radius = input$lavaan_radius,
                                                  lavaan_line_color_loop = input$lavaan_line_color_loop,
                                                  lavaan_line_alpha_loop = input$lavaan_line_alpha_loop,
                                                  lavaan_arrow_type_loop = input$lavaan_arrow_type_loop,
                                                  lavaan_arrow_size_loop = input$lavaan_arrow_size_loop,
                                                  lavaan_width_loop = input$lavaan_width_loop,
                                                  lavaan_height_loop = input$lavaan_height_loop,
                                                  lavaan_gap_size_loop = input$lavaan_gap_size_loop,
                                                  lavaan_two_way_arrow_loop = input$lavaan_two_way_arrow_loop,
                                                  flip_layout = input$flip_sem_layout,
                                                  flip_direction = input$flip_sem_layout_direction,
                                                  rotate_layout = input$rotate_sem_layout,
                                                  rotate_angle = input$rotate_sem_layout_angle,
                                                  data_file = TRUE,
                                                  which_group = group_id
        )

        # carry over bundle for reference
        values$group_storage$sem[[group_id]]$multi_group_sem_combine_menu <- input$multi_group_sem_combine

        values$group_storage$sem[[group_id]]$bundleModelObject <-  values$group_storage$sem[[multi_group_first]]$bundleModelObject
        values$group_storage$sem[[group_id]]$bundleObject <-  values$group_storage$sem[[multi_group_first]]$bundleObject
        values$group_storage$sem[[group_id]]$last_sep_by <- sep_by

        values$group_storage$sem[[group_id]]$edges <- data.frame(source = edges_from, target = edges_to)
        values$group_storage$sem[[group_id]]$nodes <- data.frame(node = node_names)

        values$group_storage$sem[[group_id]]$first_object <-  values$group_storage$sem[[multi_group_first]]$bundleObject
        values$group_storage$sem[[group_id]]$second_object <-  values$group_storage$sem[[multi_group_second]]$bundleObject
      }


      values$group_storage$sem[[group_id]]$last_sem_paths <- sem_paths

      # Nodes Coords
      values$group_storage$sem[[group_id]]$data_file <- TRUE
      values$group_storage$sem[[group_id]]$intercepts <- input$show_intercepts

      # Node shapes and sizes
      values$group_storage$sem[[group_id]]$last_latent_shape <- input$latent_shape
      values$group_storage$sem[[group_id]]$last_observed_shape <- input$observed_shape
      values$group_storage$sem[[group_id]]$last_int_shape <- input$int_shape
      values$group_storage$sem[[group_id]]$last_point_size_latent <- input$latent_size_input
      values$group_storage$sem[[group_id]]$last_point_size_observed <- input$observed_size_input
      values$group_storage$sem[[group_id]]$last_point_size_int <- input$int_size_input
      values$group_storage$sem[[group_id]]$last_width_height_ratio_latent <- input$width_height_ratio_latent
      values$group_storage$sem[[group_id]]$last_width_height_ratio_observed <- input$width_height_ratio_observed
      values$group_storage$sem[[group_id]]$last_width_height_ratio_int <- input$width_height_ratio_int

      # Line properties
      values$group_storage$sem[[group_id]]$last_line_width <- input$line_width_input
      values$group_storage$sem[[group_id]]$last_line_alpha <- input$line_alpha_input
      values$group_storage$sem[[group_id]]$last_line_endpoint_spacing <- input$line_endpoint_spacing
      values$group_storage$sem[[group_id]]$last_edge_color <- input$edge_color_input
      values$group_storage$sem[[group_id]]$last_lavaan_arrow_location <- input$lavaan_arrow_location

      # Text properties - Latent
      values$group_storage$sem[[group_id]]$last_text_size_latent <- input$text_size_latent
      values$group_storage$sem[[group_id]]$last_text_font_latent <- input$text_font_latent
      values$group_storage$sem[[group_id]]$last_text_color_latent <- input$text_color_latent
      values$group_storage$sem[[group_id]]$last_text_alpha_latent <- input$text_alpha_latent
      values$group_storage$sem[[group_id]]$last_text_fontface_latent <- input$text_fontface_latent

      # Text properties - Others (Observed/Intercept)
      values$group_storage$sem[[group_id]]$last_text_size_others <- input$text_size_others
      values$group_storage$sem[[group_id]]$last_text_font_others <- input$text_font_others
      values$group_storage$sem[[group_id]]$last_text_color_others <- input$text_color_others
      values$group_storage$sem[[group_id]]$last_text_alpha_others <- input$text_alpha_others
      values$group_storage$sem[[group_id]]$last_text_fontface_others <- input$text_fontface_others

      # Text properties - Edges
      values$group_storage$sem[[group_id]]$last_text_size_edges <- input$text_size_edges
      values$group_storage$sem[[group_id]]$last_text_font_edges <- input$text_font_edges
      values$group_storage$sem[[group_id]]$last_text_color_edges <- input$text_color_edges
      values$group_storage$sem[[group_id]]$last_text_color_fill <- text_color_fill
      values$group_storage$sem[[group_id]]$last_apply_text_color_fill <- input$apply_fill_edges_color
      values$group_storage$sem[[group_id]]$last_text_alpha_edges <- input$text_alpha_edges
      values$group_storage$sem[[group_id]]$last_text_fontface_edges <- input$text_fontface_edges

      # Point colors
      values$group_storage$sem[[group_id]]$last_point_color_latent <- input$latent_color_input
      values$group_storage$sem[[group_id]]$last_point_color_observed <- input$observed_color_input
      values$group_storage$sem[[group_id]]$last_point_color_int <- input$int_color_input

      # Node border properties
      values$group_storage$sem[[group_id]]$last_node_border_color <- input$node_border_color
      values$group_storage$sem[[group_id]]$last_node_border_width <- input$node_border_width

      # General font and arrow properties
      # values$group_storage$sem[[group_id]]$last_fontface <- input$fontface
      values$group_storage$sem[[group_id]]$last_arrow_type <- input$lavaan_arrow_type
      values$group_storage$sem[[group_id]]$last_arrow_size <- input$lavaan_arrow_size

      # Others
      values$group_storage$sem[[group_id]]$last_lavaan_syntax <- input$lavaan_syntax
      values$group_storage$sem[[group_id]]$last_sem_code <- input$sem_code
      # values$group_storage$sem[[group_id]]$data <- model_state$data
      # values$group_storage$sem[[group_id]]$data_file <- is_there_data$file
      # values$group_storage$sem[[group_id]]$last_custom_sem <- input$write_sem_code
      values$group_storage$sem[[group_id]]$original <- model_first
      values$group_storage$sem[[group_id]]$current <- model_first
      values$group_storage$sem[[group_id]]$last_group_var <- input$group_var
      values$group_storage$sem[[group_id]]$last_group_level <- input$group_level
      values$group_storage$sem[[group_id]]$last_invariance_level <- input$invariance_level
      values$group_storage$sem[[group_id]]$last_annotate_sem_est <- input$annotate_sem_est
      values$group_storage$sem[[group_id]]$last_std_est <- input$std_est
      values$group_storage$sem[[group_id]]$last_ustd_est <- input$ustd_est
      values$group_storage$sem[[group_id]]$last_conf_int <- input$ci_est
      values$group_storage$sem[[group_id]]$last_p_val <- input$pval_est
      values$group_storage$sem[[group_id]]$last_highlight_sig_path <- input$highlight_sig_path


      values$group_storage$sem[[group_id]]$first_group <- first_group_level
      values$group_storage$sem[[group_id]]$second_group <- second_group_level

      values$group_storage$sem[[group_id]]$first_group_id <- multi_group_first
      values$group_storage$sem[[group_id]]$second_group_id <- multi_group_second

      values$group_storage$sem[[group_id]]$first_model <- model_first
      values$group_storage$sem[[group_id]]$second_model <- model_second

      values$group_storage$sem[[group_id]]$last_lavaan_curved_x_shift <- input$lavaan_curved_x_shift
      values$group_storage$sem[[group_id]]$last_lavaan_curved_y_shift <- input$lavaan_curved_y_shift
      values$group_storage$sem[[group_id]]$last_remove_edgelabels <- input$remove_edgelabels
      values$group_storage$sem[[group_id]]$last_lavaan_rotate_curvature <- input$lavaan_rotate_curvature
      values$group_storage$sem[[group_id]]$last_lavaan_curvature_magnitude <- input$lavaan_curvature_magnitude
      values$group_storage$sem[[group_id]]$last_lavaan_curvature_asymmetry <- input$lavaan_curvature_asymmetry
      values$group_storage$sem[[group_id]]$last_relative_x_position <- input$relative_x_position
      values$group_storage$sem[[group_id]]$last_relative_y_position <- input$relative_y_position
      values$group_storage$sem[[group_id]]$last_center_x_position <- input$center_x_position
      values$group_storage$sem[[group_id]]$last_center_y_position <- input$center_y_position
      values$group_storage$sem[[group_id]]$last_flip_sem_layout <- input$flip_sem_layout
      values$group_storage$sem[[group_id]]$last_flip_sem_layout_direction <- input$flip_sem_layout_direction
      values$group_storage$sem[[group_id]]$last_rotate_sem_layout <- input$rotate_sem_layout
      values$group_storage$sem[[group_id]]$last_rotate_sem_layout_angle <- input$rotate_sem_layout_angle

      values$group_storage$sem[[group_id]]$last_sig_label_fontface <- input$sig_label_fontface
      values$group_storage$sem[[group_id]]$last_non_sig_label_fontface <- input$non_sig_label_fontface
      values$group_storage$sem[[group_id]]$last_sig_path_color <- input$sig_path_color
      values$group_storage$sem[[group_id]]$last_non_sig_path_color <- input$non_sig_path_color

      values$group_storage$sem[[group_id]]$last_annotate_sem_est <- input$annotate_sem_est
      values$group_storage$sem[[group_id]]$last_multigroup_data_upload <- input$multigroup_data_upload
      values$group_storage$sem[[group_id]]$last_group_var <- input$group_var
      values$group_storage$sem[[group_id]]$last_group_level <- input$group_level
      values$group_storage$sem[[group_id]]$last_p_val_alpha <- input$p_val_alpha

      # loop
      values$group_storage$sem[[group_id]]$last_residuals <- input$residuals
      values$group_storage$sem[[group_id]]$last_lavaan_radius <- input$lavaan_radius
      values$group_storage$sem[[group_id]]$last_lavaan_width_loop <- input$lavaan_width_loop
      values$group_storage$sem[[group_id]]$last_lavaan_line_color_loop <- input$lavaan_line_color_loop
      values$group_storage$sem[[group_id]]$last_lavaan_line_alpha_loop <- input$lavaan_line_alpha_loop
      values$group_storage$sem[[group_id]]$last_lavaan_arrow_type_loop <- input$lavaan_arrow_type_loop
      values$group_storage$sem[[group_id]]$last_lavaan_arrow_size_loop <- input$lavaan_arrow_size_loop
      values$group_storage$sem[[group_id]]$last_lavaan_gap_size_loop <- input$lavaan_gap_size_loop
      values$group_storage$sem[[group_id]]$last_lavaan_height_loop <- input$lavaan_height_loop
      values$group_storage$sem[[group_id]]$last_lavaan_two_way_arrow_loop <- input$lavaan_two_way_arrow_loop
      values$group_storage$sem[[group_id]]$last_lavaan_loop_offset <- input$lavaan_loop_offset
      values$group_storage$sem[[group_id]]$last_residuals_orientation_type <- input$residuals_orientation_type

      lavaan_points <- which(values$points$lavaan == TRUE & values$points$group %in% c(multi_group_first, multi_group_second))

      if (length(lavaan_points) > 0) {
        values$points <- values$points[-lavaan_points, ]
      }

      lavaan_lines <- which(values$lines$lavaan == TRUE & values$lines$group %in% c(multi_group_first, multi_group_second))
      if (length(lavaan_lines) > 0) {
        values$lines <- values$lines[-lavaan_lines, ]
      }

      lavaan_annotations <- which(values$annotations$lavaan == TRUE & values$annotations$group %in% c(multi_group_first, multi_group_second))
      if (length(lavaan_annotations) > 0) {
        values$annotations <- values$annotations[-lavaan_annotations, ]
      }

      lavaan_loops <- which(values$loops$lavaan == TRUE & values$loops$group %in% c(multi_group_first, multi_group_second))
      if (length(lavaan_loops) > 0) {
        values$loops <- values$loops[-lavaan_loops, ]
      }

      values$points <- rbind(values$points, graph_data$points)
      values$lines <- rbind(values$lines, graph_data$lines)
      values$annotations <- rbind(values$annotations, graph_data$annotations)
      values$loops <- rbind(values$loops, graph_data$loops)

      # Show notification
      showNotification("Combined SEM generated successfully.", type = "message")

      output$plot <- renderPlot({
        recreate_plot()
      })
    })
  })

  observeEvent(input$apply_changes_sem, {
    req(input$group_select)

    tryCatch({
      # req(input$lavaan_syntax)

      group_id <- as.character(input$group_select)

      if (is.null(values$group_storage$modifications[[group_id]])) {
        if (is.null(values$group_storage$modifications)) {
          values$group_storage$modifications <- list()
        }
        values$group_storage$modifications[[group_id]] <- create_mod_defaults(type = "sem")
      }

      if (is.null(values$group_storage$sem[[group_id]])) {
        if (is.null(values$group_storage$sem)) {
          values$group_storage$sem <- list()
        }
        values$group_storage$sem[[group_id]] <- create_group_storage(type = "sem")
      }

      save_state()

      multi_group_sem_layout <- input$multi_group_sem_layout

      if (multi_group_sem_layout) {

        group_to_modify <- as.character(input$modify_sem_group_select)
        group_to_match <- as.character(input$modify_sem_group_match)

        model_modify <- values$group_storage$sem[[group_to_modify]]$current
        model_match <- values$group_storage$sem[[group_to_match]]$current

        vars_modify <- get_vars_from_lavaan(model_modify)
        vars_match <- get_vars_from_lavaan(model_match)
        node_same <- identical(vars_modify, vars_match)

        group_settings_modify <- values$group_storage$sem[[group_to_modify]]
        group_settings_match <- values$group_storage$sem[[group_to_match]]

        if (node_same) {
          # lavaan_layout_matrix <- group_settings_match$last_lavaan_layout_matrix
          values$group_storage$sem[[group_to_modify]]$last_lavaan_layout_matrix0 <- group_settings_match$last_lavaan_layout_matrix
          lavaan_layout <- 'custom'
          layout_changed <- TRUE
        } else {
          lavaan_layout <- input$lavaan_layout
          lavaan_layout_matrix <- values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix
          layout_changed <- is.null(values$group_storage$sem[[group_id]]$last_lavaan_layout) || !identical(lavaan_layout, values$group_storage$sem[[group_id]]$last_lavaan_layout)
          showNotification("Cannot match: two models have different nodes/models",
                           type = "warning", duration = 5)
        }

        if (group_settings_match$last_lavaan_layout == 'layout_ai')  {
          values$group_storage$sem[[group_to_modify]]$node_coords0 <- group_settings_match$node_coords0
          values$group_storage$sem[[group_to_modify]]$last_lavaan_layout <- lavaan_layout
          layout_changed <- FALSE
        }

      } else {
        lavaan_layout <- input$lavaan_layout
        lavaan_layout_matrix <- values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix
        layout_changed <- is.null(values$group_storage$sem[[group_id]]$last_lavaan_layout) || !identical(lavaan_layout, values$group_storage$sem[[group_id]]$last_lavaan_layout)
      }

      if (lavaan_layout == 'custom') {
        lavaan_layout_matrix0 <- values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix0
      } else {
        lavaan_layout_matrix0 <- NULL
      }

      group_storage <- isolate({values$group_storage$sem[[group_id]]})

      lavaan_points0 <- values$points[values$points$lavaan & values$points$group == group_id, ]

      bundleObject <- values$group_storage$sem[[group_id]]$bundleObject
      bundleModelObject <- values$group_storage$sem[[group_id]]$bundleModelObject

      if (nrow(lavaan_points0) == 0) {
        showNotification(
          HTML(paste("No SEM (lavaan) elements in group", "<b>", group_id, "</b>", "to apply changes.")),
          type = "warning"
        )
      } else {

        #Ensure group storage exists
        if (is.null(values$group_storage$sem[[group_id]])) {
          showNotification("Group storage not initialized. Please reselect group.", type = "error")
          return()
        }

        intercepts <- ifelse (group_storage$last_multigroup_data_upload, input$show_intercepts, TRUE)

        latent_color_input <- input$latent_color_input
        observed_color_input <- input$observed_color_input
        int_color_input <- input$int_color_input
        latent_shape <- input$latent_shape
        observed_shape <- input$observed_shape
        int_shape <- input$int_shape
        latent_size_input <- input$latent_size_input
        observed_size_input <- input$observed_size_input
        int_size_input <- input$int_size_input
        width_height_ratio_latent <- input$width_height_ratio_latent
        width_height_ratio_observed <- input$width_height_ratio_observed
        width_height_ratio_int <- input$width_height_ratio_int

        node_border_color <- input$node_border_color
        node_border_width <- input$node_border_width
        param_node_select <- input$param_node_select
        modify_params_node <- input$modify_params_node
        modify_params_node_color <- input$modify_params_node_color
        modify_params_node_alpha <- input$modify_params_node_alpha
        modify_params_node_shape <- input$modify_params_node_shape
        modify_params_node_size <- input$modify_params_node_size
        modify_params_node_border_color <- input$modify_params_node_border_color
        modify_params_node_border_width <- input$modify_params_node_border_width
        modify_params_node_width_height_ratio <- input$modify_params_node_width_height_ratio

        modify_params_node_xy <- input$modify_params_node_xy
        modify_params_node_select_xy <- input$param_node_xy_select
        modify_params_node_shift_x <- safe_numeric_input(input$sem_node_shift_x)
        modify_params_node_shift_y <- safe_numeric_input(input$sem_node_shift_y)

        modify_params_latent_node_xy <- input$modify_params_latent_node_xy
        modify_params_latent_node_select_xy <- input$param_latent_node_xy_select
        modify_params_latent_node_shift_x <- safe_numeric_input(input$sem_latent_node_shift_x)
        modify_params_latent_node_shift_y <- safe_numeric_input(input$sem_latent_node_shift_y)

        modify_params_latent_node_angle <- input$modify_params_latent_node_angle
        modify_params_latent_node_select_angle <- input$param_latent_node_angle_select
        modify_params_latent_node_angle_value <- safe_numeric_input(input$sem_latent_node_angle)

        flip_sem_layout <- input$flip_sem_layout
        flip_sem_layout_direction <- input$flip_sem_layout_direction

        rotate_sem_layout <- input$rotate_sem_layout
        rotate_sem_layout_angle <- input$rotate_sem_layout_angle


        param_node_changed <- !identical(group_storage$last_param_node_select, param_node_select) ||
          !identical(group_storage$last_modify_params_node, modify_params_node) ||
          !identical(group_storage$last_modify_params_node_color, modify_params_node_color) ||
          !identical(group_storage$last_modify_params_node_alpha, modify_params_node_alpha) ||
          !identical(group_storage$last_modify_params_node_shape, modify_params_node_shape) ||
          !identical(group_storage$last_modify_params_node_size, modify_params_node_size) ||
          !identical(group_storage$last_modify_params_node_border_color, modify_params_node_border_color) ||
          !identical(group_storage$last_modify_params_node_border_width, modify_params_node_border_width) ||
          !identical(group_storage$last_modify_params_node_width_height_ratio, modify_params_node_width_height_ratio)

        if (is.na(param_node_changed)) param_node_changed <- FALSE

        line_width <- input$line_width_input
        line_alpha <- input$line_alpha_input
        line_endpoint_spacing <- input$line_endpoint_spacing
        edge_color <- input$edge_color_input

        lavaan_arrow_type <- input$lavaan_arrow_type
        lavaan_arrow_size <- input$lavaan_arrow_size
        lavaan_arrow_location <- input$lavaan_arrow_location

        param_edge_select <- input$param_edge_select
        modify_params_edge <- input$modify_params_edge
        modify_params_cov_edge <- input$modify_params_cov_edge
        modify_params_edge_color <- input$modify_params_edge_color
        modify_params_edge_linewidth <- input$modify_params_edge_linewidth
        modify_params_edge_alpha <- input$modify_params_edge_alpha
        modify_params_edge_line_style <- input$modify_params_edge_line_style
        modify_params_edge_gradient_position <- input$modify_params_edge_gradient_position
        modify_params_edge_end_color <- input$modify_params_edge_end_color
        modify_params_edge_color_type <- input$modify_params_edge_color_type
        param_cov_edge_curvature_magnitude <- input$param_cov_edge_curvature_magnitude
        param_cov_edge_select <- input$param_cov_edge_select
        param_cov_edge_rotate_curvature <- input$param_cov_edge_rotate_curvature
        param_cov_edge_curvature_asymmetry <- input$param_cov_edge_curvature_asymmetry

        param_cov_edge_x_shift <- safe_numeric_input(input$param_cov_edge_x_shift)
        param_cov_edge_y_shift <- safe_numeric_input(input$param_cov_edge_y_shift)

        modify_params_edge_xy <- input$modify_params_edge_xy

        modify_params_edge_xy_select <- input$param_edge_xy_select
        modify_params_edge_x_start <- safe_numeric_input(input$modify_params_edge_start_shift_x)
        modify_params_edge_x_end <- safe_numeric_input(input$modify_params_edge_end_shift_x)
        modify_params_edge_y_start <- safe_numeric_input(input$modify_params_edge_start_shift_y)
        modify_params_edge_y_end <- safe_numeric_input(input$modify_params_edge_end_shift_y)

        modify_params_edgelabel_xy_select <- input$param_edgelabel_xy_select

        param_edge_changed <- !identical(group_storage$last_param_edge_select, param_edge_select) ||
          !identical(group_storage$last_modify_params_edge, modify_params_edge) ||
          !identical(group_storage$last_modify_params_edge_color, modify_params_edge_color) ||
          !identical(group_storage$last_modify_params_edge_linewidth, modify_params_edge_linewidth) ||
          !identical(group_storage$last_modify_params_edge_alpha, modify_params_edge_alpha) ||
          !identical(group_storage$last_modify_params_edge_line_style, modify_params_edge_line_style) ||
          !identical(group_storage$last_modify_params_edge_gradient_position, modify_params_edge_gradient_position) ||
          !identical(group_storage$last_modify_params_edge_end_color, modify_params_edge_end_color) ||
          !identical(group_storage$last_modify_params_edge_color_type, modify_params_edge_color_type)

        if (is.na(param_edge_changed)) param_edge_changed <- FALSE

        intercepts_changed <- !identical(group_storage$intercepts, intercepts)
        if (is.na(intercepts_changed)) intercepts_changed <- FALSE

        text_size_edges <- input$text_size_edges
        text_font_edges <- input$text_font_edges
        text_color_edges <- input$text_color_edges
        apply_fill_edges_color <- input$apply_fill_edges_color
        text_color_fill <- if (apply_fill_edges_color) input$text_fill_edges else NA
        text_alpha_edges <- input$text_alpha_edges
        text_fontface_edges <- input$text_fontface_edges

        param_edgelabel_select <- input$param_edgelabel_select
        modify_params_edgelabel <- input$modify_params_edgelabel
        modify_params_edgelabel_color <- input$modify_params_edgelabel_color
        apply_modify_params_edgelabel_fill <- input$apply_modify_params_edgelabel_fill
        modify_params_edgelabel_fill <- if (apply_modify_params_edgelabel_fill) input$modify_params_edgelabel_fill else NA
        modify_params_edgelabel_size <- input$modify_params_edgelabel_size
        modify_params_edgelabel_alpha <- input$modify_params_edgelabel_alpha
        modify_params_edgelabel_angle <- input$modify_params_edgelabel_angle
        modify_params_edgelabel_font <- input$modify_params_edgelabel_font
        modify_params_edgelabel_fontface <- input$modify_params_edgelabel_fontface

        modify_params_edgelabel_xy <- input$modify_params_edgelabel_xy
        modify_params_edgelabel_xy_select <- input$param_edgelabel_xy_select
        modify_params_edgelabel_shift_x <- safe_numeric_input(input$modify_params_edgelabel_shift_x)
        modify_params_edgelabel_shift_y <- safe_numeric_input(input$modify_params_edgelabel_shift_y)


        param_edgelabel_changed <- !identical(group_storage$last_param_edgelabel_select, param_edgelabel_select) ||
          !identical(group_storage$last_modify_params_edgelabel, modify_params_edgelabel) ||
          !identical(group_storage$last_modify_params_edgelabel_color, modify_params_edgelabel_color) ||
          !identical(group_storage$last_modify_params_edgelabel_fill, modify_params_edgelabel_fill) ||
          !identical(group_storage$last_modify_params_edgelabel_size, modify_params_edgelabel_size) ||
          !identical(group_storage$last_modify_params_edgelabel_alpha, modify_params_edgelabel_alpha) ||
          !identical(group_storage$last_modify_params_edgelabel_angle, modify_params_edgelabel_angle) ||
          !identical(group_storage$last_modify_params_edgelabel_font, modify_params_edgelabel_font) ||
          !identical(group_storage$last_modify_params_edgelabel_fontface, modify_params_edgelabel_fontface)


        if (is.na(param_edgelabel_changed)) param_edgelabel_changed <- FALSE

        text_size_latent <- input$text_size_latent
        text_font_latent <- input$text_font_latent
        text_color_latent <- input$text_color_latent
        text_alpha_latent <- input$text_alpha_latent
        text_fontface_latent <- input$text_fontface_latent

        text_size_others <- input$text_size_others
        text_font_others <- input$text_font_others
        text_color_others <- input$text_color_others
        text_alpha_others <- input$text_alpha_others
        text_fontface_others <- input$text_fontface_others

        param_nodelabel_select <- input$param_nodelabel_select
        modify_params_nodelabel <- input$modify_params_nodelabel
        modify_params_nodelabel_color <- input$modify_params_nodelabel_color
        modify_params_nodelabel_size <- input$modify_params_nodelabel_size
        modify_params_nodelabel_alpha <- input$modify_params_nodelabel_alpha
        modify_params_nodelabel_angle <- input$modify_params_nodelabel_angle
        modify_params_nodelabel_font <- input$modify_params_nodelabel_font
        modify_params_nodelabel_fontface <- input$modify_params_nodelabel_fontface

        modify_params_nodelabel_xy <- input$modify_params_nodelabel_xy
        modify_params_nodelabel_xy_select <- input$param_nodelabel_xy_select
        modify_params_nodelabel_shift_x <- safe_numeric_input(input$modify_params_nodelabel_shift_x)
        modify_params_nodelabel_shift_y <- safe_numeric_input(input$modify_params_nodelabel_shift_y)

        modify_params_nodelabel_text <- input$modify_params_nodelabel_text
        params_nodelabel_text <- input$param_nodelabel_text_select
        params_nodelabel_nodelabel <- input$modify_params_nodelabel_nodelabel
        params_nodelabel_text_math_expression <- input$modify_params_nodelabel_text_math_expression

        modify_params_edgelabel_text <- input$modify_params_edgelabel_text
        params_edgelabel_text <- input$param_edgelabel_text_select
        params_edgelabel_edgelabel <- input$modify_params_edgelabel_edgelabel
        params_edgelabel_text_math_expression <- input$modify_params_edgelabel_text_math_expression

        if (!is.null(group_storage$current) && !is.null(group_storage$original) &&
            inherits(group_storage$current, c("lavaan")) && inherits(group_storage$original, c("lavaan"))) {
          model_current_changed <- !identical(lavaan::parTable(group_storage$current),
                                              lavaan::parTable(group_storage$original))
        } else {
          model_current_changed <- FALSE
        }

        param_nodelabel_changed <- !identical(group_storage$last_modify_params_nodelabel_select, param_nodelabel_select) ||
          !identical(group_storage$last_modify_params_nodelabel_color, modify_params_nodelabel_color) ||
          !identical(group_storage$last_modify_params_nodelabel_size, modify_params_nodelabel_size) ||
          !identical(group_storage$last_modify_params_nodelabel_alpha, modify_params_nodelabel_alpha) ||
          !identical(group_storage$last_modify_params_nodelabel_angle, modify_params_nodelabel_angle) ||
          !identical(group_storage$last_modify_params_nodelabel_font, modify_params_nodelabel_font) ||
          !identical(group_storage$last_modify_params_nodelabel_fontface, modify_params_nodelabel_fontface)

        if (is.na(param_nodelabel_changed)) param_nodelabel_changed <- FALSE

        param_nodelabel_text_changed <- !identical(group_storage$last_params_nodelabel_text, params_nodelabel_text) ||
          !identical(group_storage$last_params_nodelabel_nodelabel, params_nodelabel_nodelabel) ||
          !identical(group_storage$last_params_nodelabel_text_math_expression, params_nodelabel_text_math_expression)

        if (is.na(param_nodelabel_text_changed)) param_nodelabel_text_changed <- FALSE

        param_edgelabel_text_changed <- !identical(group_storage$last_params_edgelabel_text, params_edgelabel_text) ||
          !identical(group_storage$last_params_edgelabel_edgelabel, params_edgelabel_edgelabel) ||
          !identical(group_storage$last_params_edgelabel_text_math_expression, params_edgelabel_text_math_expression)

        if (is.na(param_edgelabel_text_changed)) param_edgelabel_text_changed <- FALSE

        relative_x_position <- input$relative_x_position
        relative_y_position <- input$relative_y_position
        center_x_position <- input$center_x_position
        center_y_position <- input$center_y_position

        layout_flip_changed <- !identical(flip_sem_layout, group_storage$last_flip_sem_layout) || !identical(flip_sem_layout_direction, group_storage$last_flip_sem_layout_direction)
        if (is.na(layout_flip_changed)) layout_flip_changed <- FALSE

        layout_angle_changed <- !identical(rotate_sem_layout, group_storage$last_rotate_sem_layout) || !identical(rotate_sem_layout_angle, group_storage$last_rotate_sem_layout_angle)
        if (is.na(layout_angle_changed)) layout_angle_changed <- FALSE

        scaling_changed <- !identical(relative_x_position, group_storage$last_relative_x_position) || !identical(relative_y_position, group_storage$last_relative_y_position)

        positional_shift <- !identical(center_x_position, group_storage$last_center_x_position) || !identical(center_y_position, group_storage$last_center_y_position)

        lavaan_rotate_curvature <- input$lavaan_rotate_curvature
        lavaan_curvature_magnitude <- input$lavaan_curvature_magnitude
        lavaan_curvature_asymmetry <- input$lavaan_curvature_asymmetry
        lavaan_curved_x_shift <- input$lavaan_curved_x_shift
        lavaan_curved_y_shift <- input$lavaan_curved_y_shift

        param_node_xy_changed <-  modify_params_node_xy
        param_cov_edge_changed <- modify_params_cov_edge
        param_edge_xy_changed <-  modify_params_edge_xy
        param_nodelabel_xy_changed <- modify_params_nodelabel_xy
        param_edgelabel_xy_changed <- modify_params_edgelabel_xy
        param_latent_node_xy_changed <- modify_params_latent_node_xy
        param_latent_node_angle_changed <- modify_params_latent_node_angle


        curvature_flip <- !identical(lavaan_rotate_curvature, group_storage$last_lavaan_rotate_curvature)
        curvature_changed <- !identical(lavaan_curvature_magnitude, group_storage$last_lavaan_curvature_magnitude)
        asymmetry_changed <- !identical(lavaan_curvature_asymmetry, group_storage$last_lavaan_curvature_asymmetry)
        x_shift <- !identical(lavaan_curved_x_shift, group_storage$last_lavaan_curved_x_shift)
        y_shift <- !identical(lavaan_curved_y_shift, group_storage$last_lavaan_curved_y_shift)

        sig_label_fontface <- input$sig_label_fontface
        non_sig_label_fontface <- input$non_sig_label_fontface
        sig_path_color <- input$sig_path_color
        non_sig_path_color <- input$non_sig_path_color
        annotate_sem_est <- input$annotate_sem_est
        std_est <- input$std_est
        ustd_est <- input$ustd_est
        conf_int <- input$ci_est
        p_val <- input$pval_est
        p_val_alpha <- input$p_val_alpha
        sep_by <- input$sep_by

        highlight_sig_path <- input$highlight_sig_path
        multigroup_data_upload <- group_storage$last_multigroup_data_upload
        group_var <- group_storage$last_group_var
        group_level <- group_storage$last_group_level
        invariance_level <- group_storage$last_invariance_level

        highlight_free_path <- input$highlight_free_path
        free_path_color <- input$free_path_color
        fixed_path_color <- input$fixed_path_color
        free_label_fontface <- input$free_label_fontface
        fixed_label_fontface <- input$fixed_label_fontface

        residuals <- input$residuals
        lavaan_radius <- input$lavaan_radius
        lavaan_width_loop <- input$lavaan_width_loop
        lavaan_line_color_loop <- input$lavaan_line_color_loop
        lavaan_line_alpha_loop <- input$lavaan_line_alpha_loop
        lavaan_arrow_type_loop <- input$lavaan_arrow_type_loop
        lavaan_arrow_size_loop <- input$lavaan_arrow_size_loop
        lavaan_gap_size_loop <- input$lavaan_gap_size_loop
        lavaan_height_loop <- input$lavaan_height_loop
        lavaan_two_way_arrow_loop <- input$lavaan_two_way_arrow_loop
        lavaan_loop_offset <- input$lavaan_loop_offset
        residuals_orientation_type <- input$residuals_orientation_type
        loop_names_remove <- group_storage$last_loop_names_remove_ui

        # Loop styling parameters
        modify_params_loop <- input$modify_params_loop
        modify_params_loop_color <- input$modify_params_loop_color
        modify_params_loop_alpha <- input$modify_params_loop_alpha
        modify_params_loop_radius <- input$modify_params_loop_radius
        modify_params_loop_width <- input$modify_params_loop_width
        modify_params_loop_type <- input$modify_params_loop_type
        modify_params_loop_arrow_size <- input$modify_params_loop_arrow_size
        modify_params_loop_gap_size <- input$modify_params_loop_gap_size
        modify_params_loop_two_way <- input$modify_params_loop_two_way
        param_loop_select <- input$param_loop_select

        # Loop position shift parameters
        modify_params_loop_xy <- input$modify_params_loop_xy
        modify_params_loop_select_xy <- input$param_loop_xy_select
        modify_params_loop_shift_x <- safe_numeric_input(input$sem_loop_shift_x)
        modify_params_loop_shift_y <- safe_numeric_input(input$sem_loop_shift_y)

        # Loop location parameters
        modify_params_loop_location <- input$modify_params_loop_location
        modify_params_loop_select_location <- input$param_loop_location_select
        modify_params_loop_location_value <- safe_numeric_input(input$modify_params_loop_location_value)

        # Loop label styling parameters
        modify_params_looplabel <- input$modify_params_looplabel
        modify_params_looplabel_color <- input$modify_params_looplabel_color
        apply_modify_params_looplabel_fill <- input$apply_modify_params_looplabel_fill
        modify_params_looplabel_fill <- if (apply_modify_params_looplabel_fill) input$modify_params_looplabel_fill else NA
        modify_params_looplabel_size <- input$modify_params_looplabel_size
        modify_params_looplabel_alpha <- input$modify_params_looplabel_alpha
        modify_params_looplabel_angle <- input$modify_params_looplabel_angle
        modify_params_looplabel_font <- input$modify_params_looplabel_font
        modify_params_looplabel_fontface <- input$modify_params_looplabel_fontface
        param_looplabel_select <- input$param_looplabel_select

        remove_edgelabels = input$remove_edgelabels


        # Loop label position shift parameters
        modify_params_looplabel_xy <- input$modify_params_looplabel_xy
        modify_params_looplabel_xy_select <- input$param_looplabel_xy_select
        modify_params_looplabel_shift_x <- safe_numeric_input(input$sem_looplabel_shift_x)
        modify_params_looplabel_shift_y <- safe_numeric_input(input$sem_looplabel_shift_y)

        modify_params_looplabel_text <- input$modify_params_looplabel_text
        params_looplabel_text <- input$param_looplabel_text_select
        params_looplabel_looplabel <- input$modify_params_looplabel_looplabel
        params_looplabel_text_math_expression <- input$modify_params_looplabel_text_math_expression

        param_looplabel_text_changed <- !identical(group_storage$last_params_looplabel_text, params_looplabel_text) ||
          !identical(group_storage$last_params_looplabel_looplabel, params_looplabel_looplabel) ||
          !identical(group_storage$last_params_looplabel_text_math_expression, params_looplabel_text_math_expression)

        if (is.na(param_looplabel_text_changed)) param_looplabel_text_changed <- FALSE

        residuals_remove_changed <- !identical(loop_names_remove, group_storage$last_loop_names_remove_hi)
        if (is.na(residuals_remove_changed) || is.null(residuals_remove_changed)) residuals_remove_changed <- FALSE

        residuals_exist_not_before <- (residuals == TRUE) &&
          (is.null(group_storage$last_residuals) ||
             !identical(residuals, group_storage$last_residuals))

        residuals_exist_no_longer <- (residuals == FALSE) &&
          (is.null(group_storage$last_residuals) ||
             !identical(residuals, group_storage$last_residuals))

        residuals_changed <- is.null(group_storage$last_residuals) ||
          !identical(residuals, group_storage$last_residuals) ||
          !identical(lavaan_radius, group_storage$last_lavaan_radius) ||
          !identical(lavaan_gap_size_loop, group_storage$last_lavaan_gap_size_loop) ||
          !identical(lavaan_height_loop, group_storage$last_lavaan_height_loop) ||
          !identical(lavaan_loop_offset, group_storage$last_lavaan_loop_offset) ||
          !identical(residuals_orientation_type, group_storage$last_residuals_orientation_type)

        highlight_sig_path_changed <- is.null(group_storage$last_highlight_sig_path) || !identical(highlight_sig_path, group_storage$last_highlight_sig_path)
        std_est_changed <- is.null(group_storage$last_std_est) || !identical(std_est, group_storage$last_std_est)
        ustd_est_changed <- is.null(group_storage$last_ustd_est) || !identical(ustd_est, group_storage$last_ustd_est)
        conf_int_changed <- is.null(group_storage$last_conf_int) || !identical(conf_int, group_storage$last_conf_int)
        pval_changed <- is.null(group_storage$last_p_val) || !identical(p_val, group_storage$last_p_val)
        pval_alpha_changed <- is.null(group_storage$last_p_val_alpha) || !identical(p_val_alpha, group_storage$last_p_val_alpha)
        sep_by_changed <- is.null(group_storage$last_sep_by) || !identical(sep_by, group_storage$last_sep_by)

        sem_paths <- NULL

        multi_group_sem_combine_menu <- group_storage$multi_group_sem_combine_menu
        if (is.null(multi_group_sem_combine_menu) || is.na(multi_group_sem_combine_menu)) multi_group_sem_combine_menu <- FALSE

        stats_shift <- !identical(sig_label_fontface, group_storage$last_sig_label_fontface) ||
          !identical(non_sig_label_fontface, group_storage$last_non_sig_label_fontface) ||
          !identical(sig_path_color, group_storage$last_sig_path_color) ||
          !identical(non_sig_path_color, group_storage$last_non_sig_path_color) ||
          !identical(std_est, group_storage$last_std_est) ||
          !identical(ustd_est, group_storage$last_ustd_est) ||
          !identical(conf_int, group_storage$last_conf_int) ||
          !identical(p_val, group_storage$last_p_val) ||
          !identical(p_val_alpha, group_storage$last_p_val_alpha) ||
          !identical(highlight_sig_path, group_storage$last_highlight_sig_path) ||
          !identical(remove_edgelabels, group_storage$last_remove_edgelabels) ||
          sep_by_changed


        if (is.na(stats_shift)) stats_shift <- FALSE

        if (!is.null(bundleObject)) {
          edge_label_file <- TRUE
          if (is(bundleObject)[[1]] == "lavaan") {

            if (!multi_group_sem_combine_menu) {
              layout_algorithm <-
                if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                  lavaan_layout_matrix0
                } else if (lavaan_layout == 'layout_ai') {
                  "default"
                } else {
                  lavaan_layout
                }

              group_labels <- lavInspect(group_storage$current, "group.label")
              if (length(group_labels) == 0) {
                multigroup_data_upload <- FALSE
              } else {
                multigroup_data_upload <- TRUE
              }

              group_var <- NULL

              if (!any(layout_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed, sep_by_changed, residuals_changed)) {
                sem_paths <- group_storage$last_sem_paths
              } else {
                sem_paths <- lavaan_to_sempaths(fit = group_storage$current, # group_storage$current = current model, bundleObject only use it for class type
                                                data_file = group_storage$data,
                                                layout_algorithm = layout_algorithm,
                                                annotate_estimates = annotate_sem_est,
                                                standardized = std_est,
                                                unstandardized = ustd_est,
                                                conf_int = conf_int,
                                                p_val = p_val,
                                                multi_group = multigroup_data_upload,
                                                group_var = group_var,
                                                group_level = group_level,
                                                p_val_alpha = p_val_alpha,
                                                residuals = residuals)
              }

              values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
              values$group_storage$sem[[group_id]]$last_std_est <- std_est
              values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
              values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
              values$group_storage$sem[[group_id]]$last_p_val <- p_val
              values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
              values$group_storage$sem[[group_id]]$last_multigroup_data_upload <- multigroup_data_upload

            } else {
              first_group_level <- values$group_storage$sem[[group_id]]$first_group
              second_group_level <- values$group_storage$sem[[group_id]]$second_group

              lavaan_fit <- values$group_storage$sem[[group_id]]$current

              combine <- values$group_storage$sem[[group_id]]$multi_combine_real

              if (!combine) {
                model_first <- values$group_storage$sem[[group_id]]$first_model
                model_second <- values$group_storage$sem[[group_id]]$second_model
                lavaan_fit <- list(model_first, model_second)
              }

              layout_algorithm <-
                if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                  lavaan_layout_matrix0
                } else if (lavaan_layout == 'layout_ai') {
                  "default"
                } else {
                  lavaan_layout
                }

              if (!any(layout_changed, highlight_sig_path_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed,
                       sep_by_changed, residuals_changed)) {
                sem_paths <- group_storage$last_sem_paths
              } else {
                sem_paths <- lavaan_to_sempaths(fit = lavaan_fit,
                                                layout_algorithm = layout_algorithm,
                                                intercepts = TRUE,
                                                annotate_estimates = annotate_sem_est,
                                                standardized = std_est,
                                                unstandardized = ustd_est,
                                                conf_int = conf_int,
                                                p_val = p_val,
                                                combine = combine,
                                                group1 = first_group_level,
                                                group2 = second_group_level,
                                                sep_by = sep_by,
                                                residuals = residuals)
              }

              values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
              values$group_storage$sem[[group_id]]$last_std_est <- std_est
              values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
              values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
              values$group_storage$sem[[group_id]]$last_p_val <- p_val
              values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
              values$group_storage$sem[[group_id]]$last_sep_by <- sep_by
            }

          } else if (is(bundleObject)[[1]] == "blavaan") {
            edge_label_file <- TRUE

            if (!multi_group_sem_combine_menu) {
              layout_algorithm <-
                if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                  lavaan_layout_matrix0
                } else if (lavaan_layout == 'layout_ai') {
                  "default"
                } else {
                  lavaan_layout
                }

              group_labels <- lavInspect(group_storage$current, "group.label")
              if (length(group_labels) == 0) {
                multigroup_data_upload <- FALSE
              } else {
                multigroup_data_upload <- TRUE
              }

              group_var <- NULL

              if (!any(layout_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed, sep_by_changed, residuals_changed)) {
                sem_paths <- group_storage$last_sem_paths
              } else {
                sem_paths <- blavaan_to_sempaths(fit = group_storage$current,
                                                 data_file = group_storage$data,
                                                 layout_algorithm = layout_algorithm,
                                                 annotate_estimates = annotate_sem_est,
                                                 standardized = std_est,
                                                 unstandardized = ustd_est,
                                                 conf_int = conf_int,
                                                 p_val = p_val,
                                                 multi_group = multigroup_data_upload,
                                                 group_var = group_var,
                                                 group_level = group_level,
                                                 residuals = residuals)
              }


              values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
              values$group_storage$sem[[group_id]]$last_std_est <- std_est
              values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
              values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
              values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
              values$group_storage$sem[[group_id]]$last_p_val <- p_val
              values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
              values$group_storage$sem[[group_id]]$last_multigroup_data_upload <- multigroup_data_upload

            } else {

              first_group_level <- values$group_storage$sem[[group_id]]$first_group
              second_group_level <- values$group_storage$sem[[group_id]]$second_group

              lavaan_fit <- values$group_storage$sem[[group_id]]$current

              combine <- values$group_storage$sem[[group_id]]$multi_combine_real

              if (!combine) {
                model_first <- values$group_storage$sem[[group_id]]$first_model
                model_second <- values$group_storage$sem[[group_id]]$second_model
                lavaan_fit <- list(model_first, model_second)
              }

              layout_algorithm <-
                if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                  lavaan_layout_matrix0
                } else if (lavaan_layout == 'layout_ai') {
                  "default"
                } else {
                  lavaan_layout
                }

              if (!any(layout_changed, highlight_sig_path_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed,
                       sep_by_changed, residuals_changed)) {
                sem_paths <- group_storage$last_sem_paths
              } else {
                sem_paths <- blavaan_to_sempaths(fit = lavaan_fit,
                                                 layout_algorithm = layout_algorithm,
                                                 intercepts = TRUE,
                                                 annotate_estimates = annotate_sem_est,
                                                 standardized = std_est,
                                                 unstandardized = ustd_est,
                                                 conf_int = conf_int,
                                                 p_val = p_val,
                                                 combine = combine,
                                                 group1 = first_group_level,
                                                 group2 = second_group_level,
                                                 sep_by = sep_by,
                                                 residuals = residuals)
              }

              values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
              values$group_storage$sem[[group_id]]$last_std_est <- std_est
              values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
              values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
              values$group_storage$sem[[group_id]]$last_p_val <- p_val
              values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
              values$group_storage$sem[[group_id]]$last_sep_by <- sep_by
            }

          } else if (inherits(bundleObject, "mplusObject")) {

            layout_algorithm <-
              if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                lavaan_layout_matrix0
              } else if (lavaan_layout == 'layout_ai') {
                "default"
              } else {
                lavaan_layout
              }

            if (!any(layout_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed, sep_by_changed, residuals_changed)) {
              sem_paths <- group_storage$last_sem_paths
            } else {
              sem_paths <- lavaan_to_sempaths(fit = group_storage$current,
                                              data_file = group_storage$data,
                                              layout_algorithm = layout_algorithm,
                                              annotate_estimates = annotate_sem_est,
                                              standardized = std_est,
                                              unstandardized = ustd_est,
                                              conf_int = conf_int,
                                              p_val = p_val,
                                              p_val_alpha = p_val_alpha,
                                              residuals = residuals)
            }

            values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
            values$group_storage$sem[[group_id]]$last_std_est <- std_est
            values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
            values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
            values$group_storage$sem[[group_id]]$last_p_val <- p_val
            values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha

          } else if (inherits(bundleObject, "MxRAMModel")) {

            if (!multi_group_sem_combine_menu) {

              layout_algorithm <-
                if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                  lavaan_layout_matrix0
                } else if (lavaan_layout == 'layout_ai') {
                  "default"
                } else {
                  lavaan_layout
                }

              if (!any(layout_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed, sep_by_changed, residuals_changed)) {
                sem_paths <- group_storage$last_sem_paths
              } else {
                sem_paths <- lavaan_to_sempaths(fit = group_storage$current,
                                                data_file = group_storage$data,
                                                layout_algorithm = layout_algorithm,
                                                annotate_estimates = annotate_sem_est,
                                                standardized = std_est,
                                                unstandardized = ustd_est,
                                                conf_int = conf_int,
                                                p_val = p_val,
                                                p_val_alpha = p_val_alpha,
                                                residuals = residuals)
              }

              values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
              values$group_storage$sem[[group_id]]$last_std_est <- std_est
              values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
              values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
              values$group_storage$sem[[group_id]]$last_p_val <- p_val
              values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
            } else {# combined

              combine <- values$group_storage$sem[[group_id]]$multi_combine_real

              first_group_level <- values$group_storage$sem[[group_id]]$first_group
              second_group_level <- values$group_storage$sem[[group_id]]$second_group

              if (!combine) {
                model_first <- values$group_storage$sem[[group_id]]$first_model
                model_second <- values$group_storage$sem[[group_id]]$second_model

              }

              layout_algorithm <-
                if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                  lavaan_layout_matrix0
                } else if (lavaan_layout == 'layout_ai') {
                  "default"
                } else {
                  lavaan_layout
                }

              if (!any(layout_changed, highlight_sig_path_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed,
                       sep_by_changed, residuals_changed)) {
                sem_paths <- group_storage$last_sem_paths
              } else {
                sem_paths <- lavaan_to_sempaths(fit = list(model_first, model_second),
                                                layout_algorithm = layout_algorithm,
                                                intercepts = TRUE,
                                                annotate_estimates = annotate_sem_est,
                                                standardized = std_est,
                                                unstandardized = ustd_est,
                                                conf_int = conf_int,
                                                p_val = p_val,
                                                combine = FALSE,
                                                group1 = first_group_level,
                                                group2 = second_group_level,
                                                sep_by = sep_by,
                                                residuals = residuals)
              }

              values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
              values$group_storage$sem[[group_id]]$last_std_est <- std_est
              values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
              values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
              values$group_storage$sem[[group_id]]$last_p_val <- p_val
              values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
              values$group_storage$sem[[group_id]]$last_sep_by <- sep_by
            }

          } else if (inherits(bundleObject, "qgraph")) { # semPaths

            if (is.null(bundleModelObject)) {
              sem_paths <- bundleObject # CANT CHANGE LAYOUT BECAUSE NO MODEL IS PROVIDED
            } else {
              if (is(bundleModelObject)[[1]] == "lavaan") {

                if (!multi_group_sem_combine_menu) {
                  layout_algorithm <-
                    if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                      lavaan_layout_matrix0
                    } else if (lavaan_layout == 'layout_ai') {
                      "default"
                    } else {
                      lavaan_layout
                    }

                  group_labels <- lavInspect(group_storage$current, "group.label")
                  if (length(group_labels) == 0) {
                    multigroup_data_upload <- FALSE
                  } else {
                    multigroup_data_upload <- TRUE
                  }

                  group_var <- NULL

                  if (!any(layout_changed, highlight_sig_path_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed,
                           sep_by_changed, residuals_changed)) {
                    sem_paths <- group_storage$last_sem_paths
                  } else {
                    sem_paths <- lavaan_to_sempaths(fit = group_storage$current,
                                                    data_file = group_storage$data,
                                                    layout_algorithm = layout_algorithm,
                                                    annotate_estimates = annotate_sem_est,
                                                    standardized = std_est,
                                                    unstandardized = ustd_est,
                                                    conf_int = conf_int,
                                                    p_val = p_val,
                                                    multi_group = multigroup_data_upload,
                                                    group_var = group_var,
                                                    group_level = group_level,
                                                    p_val_alpha = p_val_alpha,
                                                    residuals = residuals)
                  }


                  values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
                  values$group_storage$sem[[group_id]]$last_std_est <- std_est
                  values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
                  values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
                  values$group_storage$sem[[group_id]]$last_p_val <- p_val
                  values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
                  values$group_storage$sem[[group_id]]$last_multigroup_data_upload <- multigroup_data_upload

                } else { # combined
                  first_group_level <- values$group_storage$sem[[group_id]]$first_group
                  second_group_level <- values$group_storage$sem[[group_id]]$second_group

                  lavaan_fit <- values$group_storage$sem[[group_id]]$current
                  combine <- values$group_storage$sem[[group_id]]$multi_combine_real

                  if (!combine) {
                    model_first <- values$group_storage$sem[[group_id]]$first_model
                    model_second <- values$group_storage$sem[[group_id]]$second_model
                    lavaan_fit <- list(model_first, model_second)
                  }

                  layout_algorithm <-
                    if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                      lavaan_layout_matrix0
                    } else if (lavaan_layout == 'layout_ai') {
                      "default"
                    } else {
                      lavaan_layout
                    }

                  if (!any(layout_changed, highlight_sig_path_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed,
                           sep_by_changed, residuals_changed)) {
                    sem_paths <- group_storage$last_sem_paths
                  } else {
                    sem_paths <- lavaan_to_sempaths(fit = lavaan_fit,
                                                    layout_algorithm = layout_algorithm,
                                                    intercepts = TRUE,
                                                    annotate_estimates = annotate_sem_est,
                                                    standardized = std_est,
                                                    unstandardized = ustd_est,
                                                    conf_int = conf_int,
                                                    p_val = p_val,
                                                    combine = combine,
                                                    group1 = first_group_level,
                                                    group2 = second_group_level,
                                                    sep_by = sep_by,
                                                    residuals = residuals)
                  }

                  values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
                  values$group_storage$sem[[group_id]]$last_std_est <- std_est
                  values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
                  values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
                  values$group_storage$sem[[group_id]]$last_p_val <- p_val
                  values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
                  values$group_storage$sem[[group_id]]$last_sep_by <- sep_by

                }

              } else if (is(bundleModelObject)[[1]] == "blavaan") {

                if (!multi_group_sem_combine_menu) {
                  layout_algorithm <-
                    if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                      lavaan_layout_matrix0
                    } else if (lavaan_layout == 'layout_ai') {
                      "default"
                    } else {
                      lavaan_layout
                    }

                  group_labels <- lavInspect(group_storage$current, "group.label")
                  if (length(group_labels) == 0) {
                    multigroup_data_upload <- FALSE
                  } else {
                    multigroup_data_upload <- TRUE
                  }

                  group_var <- NULL

                  if (!any(layout_changed, highlight_sig_path_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed,
                           sep_by_changed, residuals_changed)) {
                    sem_paths <- group_storage$last_sem_paths
                  } else {
                    sem_paths <- blavaan_to_sempaths(fit = group_storage$current,
                                                     data_file = group_storage$data,
                                                     layout_algorithm = layout_algorithm,
                                                     annotate_estimates = annotate_sem_est,
                                                     standardized = std_est,
                                                     unstandardized = ustd_est,
                                                     conf_int = conf_int,
                                                     p_val = p_val,
                                                     multi_group = multigroup_data_upload,
                                                     group_var = group_var,
                                                     group_level = group_level,
                                                     residuals = residuals)
                  }

                  values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
                  values$group_storage$sem[[group_id]]$last_std_est <- std_est
                  values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
                  values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
                  values$group_storage$sem[[group_id]]$last_p_val <- p_val
                  values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
                  values$group_storage$sem[[group_id]]$last_multigroup_data_upload <- multigroup_data_upload

                } else {

                  first_group_level <- values$group_storage$sem[[group_id]]$first_group
                  second_group_level <- values$group_storage$sem[[group_id]]$second_group

                  lavaan_fit <- values$group_storage$sem[[group_id]]$current
                  combine <- values$group_storage$sem[[group_id]]$multi_combine_real

                  if (!combine) {
                    model_first <- values$group_storage$sem[[group_id]]$first_model
                    model_second <- values$group_storage$sem[[group_id]]$second_model
                    lavaan_fit <- list(model_first, model_second)
                  }

                  layout_algorithm <-
                    if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                      lavaan_layout_matrix0
                    } else if (lavaan_layout == 'layout_ai') {
                      "default"
                    } else {
                      lavaan_layout
                    }

                  if (!any(layout_changed, highlight_sig_path_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed,
                           sep_by_changed, residuals_changed)) {
                    sem_paths <- group_storage$last_sem_paths
                  } else {
                    sem_paths <- blavaan_to_sempaths(fit = lavaan_fit,
                                                     layout_algorithm = layout_algorithm,
                                                     intercepts = TRUE,
                                                     annotate_estimates = annotate_sem_est,
                                                     standardized = std_est,
                                                     unstandardized = ustd_est,
                                                     conf_int = conf_int,
                                                     p_val = p_val,
                                                     combine = combine,
                                                     group1 = first_group_level,
                                                     group2 = second_group_level,
                                                     sep_by = sep_by,
                                                     residuals = residuals)
                  }

                  values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
                  values$group_storage$sem[[group_id]]$last_std_est <- std_est
                  values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
                  values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
                  values$group_storage$sem[[group_id]]$last_p_val <- p_val
                  values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
                  values$group_storage$sem[[group_id]]$last_sep_by <- sep_by
                }

              }
            }
          } else if (inherits(bundleObject, "sem_graph")) { # tidySEM
            if (!multi_group_sem_combine_menu) {

              fit_delta <- bundleObject
              if (inherits(bundleModelObject, "lavaan")) {
                if (!is.null(group_storage$current) && !is.null(group_storage$original)) {
                  if (!identical(lavaan::parTable(group_storage$current), lavaan::parTable(group_storage$original))) {
                    fit_delta <- tidySEM::prepare_graph(model = group_storage$current) # in case model parameters are modified
                  }
                }
              }

              if (is(bundleModelObject)[[1]] == "lavaan") {
                fit_delta <- update_tidysem_labels(fit_delta, standardized = std_est, unstandardized = ustd_est,
                                                   p_val = p_val, conf_int = conf_int)

                sem_paths <- update_tidysem_labels(bundleObject, standardized = std_est, unstandardized = ustd_est,
                                                   p_val = p_val, conf_int = conf_int)

              } else if (is(bundleModelObject)[[1]] == "blavaan") {
                fit_delta <- update_tidysem_labels_bayes(fit_delta, bundleModelObject, standardized = std_est, unstandardized = ustd_est,
                                                         p_val = p_val, conf_int = conf_int)

                sem_paths <- update_tidysem_labels_bayes(bundleObject, bundleModelObject, standardized = std_est, unstandardized = ustd_est,
                                                         p_val = p_val, conf_int = conf_int)
              }


              values$group_storage$sem[[group_id]]$last_p_val <- p_val
              values$group_storage$sem[[group_id]]$last_std_est <- std_est
              values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
              values$group_storage$sem[[group_id]]$last_conf_int <- conf_int

            } else {
              first_group_level <- values$group_storage$sem[[group_id]]$first_group
              second_group_level <- values$group_storage$sem[[group_id]]$second_group

              combine <- values$group_storage$sem[[group_id]]$multi_combine_real

              if (combine) {
                if (is(bundleModelObject)[[1]] == "lavaan") {
                  sem_paths <- combine_tidysem_groups(bundleObject, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                      standardized = std_est, unstandardized = ustd_est, p_val = p_val, conf_int = conf_int)
                  fit_delta <- combine_tidysem_groups(bundleObject, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                      standardized = std_est, unstandardized = ustd_est, p_val = p_val, conf_int = conf_int)


                } else if (is(bundleModelObject)[[1]] == "blavaan") {
                  sem_paths <- combine_tidysem_groups_bayes(bundleObject, bundleModelObject, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                            standardized = std_est, unstandardized = ustd_est, p_val = p_val, conf_int = conf_int)
                  fit_delta <- combine_tidysem_groups_bayes(bundleObject, bundleModelObject, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                            standardized = std_est, unstandardized = ustd_est, p_val = p_val, conf_int = conf_int)
                }
              } else {
                if (is(bundleModelObject)[[1]] == "lavaan") {
                  first_object <- values$group_storage$sem[[group_id]]$first_object
                  second_object <- values$group_storage$sem[[group_id]]$second_object

                  sem_paths <- combine_tidysem_objects(first_object, second_object, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                       standardized = std_est, unstandardized = ustd_est, p_val = p_val, conf_int = conf_int)
                  fit_delta <- combine_tidysem_objects(first_object, second_object, group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                       standardized = std_est, unstandardized = ustd_est, p_val = p_val, conf_int = conf_int)

                } else if (is(bundleModelObject)[[1]] == "blavaan") {
                  first_object <- values$group_storage$sem[[group_id]]$first_object
                  second_object <- values$group_storage$sem[[group_id]]$second_object
                  first_model <- values$group_storage$sem[[group_id]]$first_model
                  second_model <- values$group_storage$sem[[group_id]]$second_model

                  sem_paths <- combine_tidysem_objects_bayes(first_object, second_object, first_model, second_model,
                                                             group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                             standardized = std_est, unstandardized = ustd_est, p_val = p_val, conf_int = conf_int)
                  fit_delta <- combine_tidysem_objects_bayes(first_object, second_object, first_model, second_model,
                                                             group1 = first_group_level, group2 = second_group_level, sep_by = sep_by,
                                                             standardized = std_est, unstandardized = ustd_est, p_val = p_val, conf_int = conf_int)
                }
              }

              values$group_storage$sem[[group_id]]$last_sep_by <- sep_by
              values$group_storage$sem[[group_id]]$last_std_est <- std_est
              values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
              values$group_storage$sem[[group_id]]$last_p_val <- p_val
              values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
            }

          } else if (inherits(bundleObject, "grViz")) { # diagrammeR / lavaanPlot
            grviz_object <- bundleObject
            if (is.null(sem_paths) || is.na(sem_pathS)) sem_paths <- NULL
          } else {
            stop("Unsupported object type in bundle.")
          }
        } else {

          edge_label_file <- sem_file_data()

          if (!multi_group_sem_combine_menu) {

            layout_algorithm <-
              if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                lavaan_layout_matrix0
              } else if (lavaan_layout == 'layout_ai') {
                "default"
              } else {
                lavaan_layout
              }


            sem_paths <- lavaan_to_sempaths(fit = group_storage$current,
                                            data_file = group_storage$data,
                                            layout_algorithm = layout_algorithm,
                                            intercepts = intercepts,
                                            annotate_estimates = annotate_sem_est,
                                            standardized = std_est,
                                            unstandardized = ustd_est,
                                            conf_int = conf_int,
                                            p_val = p_val,
                                            multi_group = multigroup_data_upload,
                                            group_var = group_var,
                                            group_level = group_level,
                                            p_val_alpha = p_val_alpha,
                                            residuals = residuals)


            values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
            values$group_storage$sem[[group_id]]$last_std_est <- std_est
            values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
            values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
            values$group_storage$sem[[group_id]]$last_p_val <- p_val
            values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
            values$group_storage$sem[[group_id]]$last_multigroup_data_upload <- multigroup_data_upload

          } else { # combined
            first_group_level <- values$group_storage$sem[[group_id]]$first_group
            second_group_level <- values$group_storage$sem[[group_id]]$second_group

            lavaan_fit <- values$group_storage$sem[[group_id]]$current

            layout_algorithm <-
              if (lavaan_layout == 'custom' && !is.null(lavaan_layout_matrix0)) {
                lavaan_layout_matrix0
              } else if (lavaan_layout == 'layout_ai') {
                "default"
              } else {
                lavaan_layout
              }


            if (!any(layout_changed, highlight_sig_path_changed, std_est_changed, ustd_est_changed, conf_int_changed, pval_changed, pval_alpha_changed,
                     sep_by_changed)) {
              sem_paths <- group_storage$last_sem_paths
            } else {
              sem_paths <- lavaan_to_sempaths(fit = lavaan_fit,
                                              layout_algorithm = layout_algorithm,
                                              intercepts = TRUE,
                                              annotate_estimates = input$annotate_sem_est,
                                              standardized = input$std_est,
                                              unstandardized = input$ustd_est,
                                              p_val = input$pval_est,
                                              combine = TRUE,
                                              group1 = first_group_level,
                                              group2 = second_group_level,
                                              sep_by = sep_by,
                                              residuals = residuals)
            }

            values$group_storage$sem[[group_id]]$last_annotate_sem_est <- annotate_sem_est
            values$group_storage$sem[[group_id]]$last_std_est <- std_est
            values$group_storage$sem[[group_id]]$last_ustd_est <- ustd_est
            values$group_storage$sem[[group_id]]$last_conf_int <- conf_int
            values$group_storage$sem[[group_id]]$last_p_val <- p_val
            values$group_storage$sem[[group_id]]$last_p_val_alpha <- p_val_alpha
            values$group_storage$sem[[group_id]]$last_sep_by <- sep_by

          }

        }

        lavaan_layout_matrix <- sem_paths$layout
        lavaan_layout_matrix0 <- sem_paths$layout

        if (inherits(bundleObject, "sem_graph")) {
          edge_label_file <- TRUE
        } else {
          edge_label_file <- sem_file_data()
        }

        modifications <- values$group_storage$modifications[[group_id]]

        if (modify_params_node == TRUE && !is.null(param_node_select) && length(param_node_select) > 0) {
          new_node_mod <- data.frame(
            text = param_node_select,
            color = modify_params_node_color,
            alpha = modify_params_node_alpha,
            shape = modify_params_node_shape,
            size = modify_params_node_size,
            border_color = modify_params_node_border_color,
            border_width = modify_params_node_border_width,
            width_height_ratio = modify_params_node_width_height_ratio,
            stringsAsFactors = FALSE
          )

          modifications$node <- rbind(modifications$node, new_node_mod)

        }

        if (modify_params_edge == TRUE && !is.null(param_edge_select) && length(param_edge_select) > 0) {
          # Split all edges at once
          edge_specs <- strsplit(param_edge_select, " to ")

          # Create data frame for all edges
          new_mod_edges <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            color = modify_params_edge_color,
            width = modify_params_edge_linewidth,
            alpha = modify_params_edge_alpha,
            line_style = modify_params_edge_line_style,
            color_type = modify_params_edge_color_type,
            end_color = modify_params_edge_end_color,
            gradient_position = modify_params_edge_gradient_position,
            stringsAsFactors = FALSE
          )

          modifications$edge <- rbind(
            modifications$edge,
            new_mod_edges
          )
        }

        if (modify_params_edgelabel == TRUE && !is.null(param_edgelabel_select) && length(param_edgelabel_select) > 0) {
          # Split all edges at once
          edge_specs <- strsplit(param_edgelabel_select, " to ")

          # Create data frame for all edge labels
          new_mod_labels <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            color = modify_params_edgelabel_color,
            fill = modify_params_edgelabel_fill,
            size = modify_params_edgelabel_size,
            alpha = modify_params_edgelabel_alpha,
            angle = modify_params_edgelabel_angle,
            font = modify_params_edgelabel_font,
            fontface = modify_params_edgelabel_fontface,
            stringsAsFactors = FALSE
          )

          modifications$edgelabel <- rbind(
            modifications$edgelabel,
            new_mod_labels
          )
        }

        if (modify_params_node_xy == TRUE && !is.null(modify_params_node_select_xy) && length(modify_params_node_select_xy) > 0) {
          new_mod_label <- data.frame(
            text = modify_params_node_select_xy,
            x_shift = modify_params_node_shift_x,
            y_shift = modify_params_node_shift_y,
            stringsAsFactors = FALSE
          )

          modifications$node_xy <- rbind(
            modifications$node_xy,
            new_mod_label)
        }

        if (modify_params_latent_node_xy == TRUE && !is.null(modify_params_latent_node_select_xy) && length(modify_params_latent_node_select_xy) > 0) {
          latent_mod  <- data.frame(
            text = modify_params_latent_node_select_xy,
            x_shift = modify_params_latent_node_shift_x,
            y_shift = modify_params_latent_node_shift_y,
            node_type = "latent",
            stringsAsFactors = FALSE
          )

          if (!is.null(group_storage$current)) {
            observed_vars <- get_observed_variables(modify_params_latent_node_select_xy, group_storage$current)
            intercept_vars <- get_intercept_variables(modify_params_latent_node_select_xy, group_storage$current)
          } else {
            observed_vars <- NULL
            intercept_vars <- NULL
          }


          if (length(observed_vars) > 0 || length(intercept_vars) > 0) {
            observed_mods <- data.frame(
              text = observed_vars,
              x_shift = modify_params_latent_node_shift_x,
              y_shift = modify_params_latent_node_shift_y,
              node_type = "observed",
              stringsAsFactors = FALSE
            )

            intercept_mods <- data.frame(
              text = intercept_vars,
              x_shift = modify_params_latent_node_shift_x,
              y_shift = modify_params_latent_node_shift_y,
              node_type = "intercept",
              stringsAsFactors = FALSE
            )

            new_mod_label <- rbind(latent_mod, observed_mods, intercept_mods)
          } else {
            new_mod_label <- latent_mod
          }

          modifications$latent_node_xy <- rbind(
            modifications$latent_node_xy,
            new_mod_label)
        }

        if (modify_params_latent_node_angle == TRUE && !is.null(modify_params_latent_node_select_angle) && length(modify_params_latent_node_select_angle) > 0) {
          latent_mod  <- data.frame(
            text = modify_params_latent_node_select_angle,
            angle = modify_params_latent_node_angle_value,
            node_type = "latent",
            stringsAsFactors = FALSE
          )

          if (!is.null(group_storage$current)) {
            observed_vars <- get_observed_variables(modify_params_latent_node_select_angle, group_storage$current)
            intercept_vars <- get_intercept_variables(modify_params_latent_node_select_angle, group_storage$current)
          } else {
            observed_vars <- NULL
            intercept_vars <- NULL
          }


          if (length(observed_vars) > 0 || length(intercept_vars) > 0) {
            observed_mods <- data.frame(
              text = observed_vars,
              angle = modify_params_latent_node_angle_value,
              node_type = "observed",
              stringsAsFactors = FALSE
            )

            intercept_mods <- data.frame(
              text = intercept_vars,
              angle = modify_params_latent_node_angle_value,
              node_type = "intercept",
              stringsAsFactors = FALSE
            )

            new_mod_label <- rbind(latent_mod, observed_mods, intercept_mods)
          } else {
            new_mod_label <- latent_mod
          }

          modifications$latent_node_angle <- rbind(
            modifications$latent_node_angle,
            new_mod_label)
        }

        if (modify_params_edge_xy == TRUE && !is.null(modify_params_edge_xy_select) && length(modify_params_edge_xy_select) > 0) {
          edge_specs <- strsplit(modify_params_edge_xy_select, " to ")

          new_mod_edge_xy <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            start_x_shift = modify_params_edge_x_start,
            start_y_shift = modify_params_edge_y_start,
            end_x_shift = modify_params_edge_x_end,
            end_y_shift = modify_params_edge_y_end,
            stringsAsFactors = FALSE
          )

          modifications$edge_xy <- rbind(
            modifications$edge_xy,
            new_mod_edge_xy
          )
        }

        if (modify_params_edgelabel_xy == TRUE && !is.null(modify_params_edgelabel_xy_select) && length(modify_params_edgelabel_xy_select) > 0) {
          edge_specs <- strsplit(modify_params_edgelabel_xy_select, " to ")

          new_mod_edgelabels_xy <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            x_shift = modify_params_edgelabel_shift_x,
            y_shift = modify_params_edgelabel_shift_y,
            stringsAsFactors = FALSE
          )

          modifications$edgelabel_xy <- rbind(
            modifications$edgelabel_xy,
            new_mod_edgelabels_xy
          )
        }

        if (modify_params_edgelabel_text == TRUE && !is.null(params_edgelabel_text) && length(params_edgelabel_text) > 0) {
          edge_specs <- strsplit(params_edgelabel_text, " to ")

          new_edge_label_text <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            text = params_edgelabel_edgelabel,
            math_expression = params_edgelabel_text_math_expression,
            stringsAsFactors = FALSE
          )

          modifications$edgelabel_text <- rbind(
            modifications$edgelabel_text,
            new_edge_label_text
          )
        }

        if (modify_params_cov_edge == TRUE && !is.null(param_cov_edge_select) && length(param_cov_edge_select) > 0) {
          edge_specs <- strsplit(param_cov_edge_select, " to ")

          new_mod_cov_edges <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            curvature_magnitude = param_cov_edge_curvature_magnitude,
            rotate_curvature = param_cov_edge_rotate_curvature,
            curvature_asymmetry = param_cov_edge_curvature_asymmetry,
            x_shift = param_cov_edge_x_shift,
            y_shift = param_cov_edge_y_shift,
            stringsAsFactors = FALSE
          )

          modifications$cov_edge <- rbind(
            modifications$cov_edge,
            new_mod_cov_edges
          )
        }

        if (modify_params_nodelabel == TRUE && !is.null(param_nodelabel_select) && length(param_nodelabel_select) > 0) {

          new_mod_label <- data.frame(
            text = param_nodelabel_select,
            color = modify_params_nodelabel_color,
            size = modify_params_nodelabel_size,
            alpha = modify_params_nodelabel_alpha,
            angle = modify_params_nodelabel_angle,
            font = modify_params_nodelabel_font,
            fontface = modify_params_nodelabel_fontface,
            stringsAsFactors = FALSE
          )

          modifications$nodelabel <- rbind(
            modifications$nodelabel,
            new_mod_label
          )

        }

        if (modify_params_nodelabel_xy == TRUE && !is.null(modify_params_nodelabel_xy_select) && length(modify_params_nodelabel_xy_select) > 0) {
          new_mod_label <- data.frame(
            text = modify_params_nodelabel_xy_select,
            x_shift = modify_params_nodelabel_shift_x,
            y_shift = modify_params_nodelabel_shift_y,
            stringsAsFactors = FALSE
          )

          modifications$nodelabel_xy <- rbind(
            modifications$nodelabel_xy,
            new_mod_label
          )

        }

        if (modify_params_nodelabel_text == TRUE && !is.null(params_nodelabel_text) && length(params_nodelabel_text) > 0) {
          new_mod_label <- data.frame(
            text = params_nodelabel_text,
            nodelabel = params_nodelabel_nodelabel,
            math_expression = params_nodelabel_text_math_expression,
            stringsAsFactors = FALSE
          )

          modifications$nodelabel_text <- rbind(
            modifications$nodelabel_text,
            new_mod_label
          )
        }

        if (modify_params_loop == TRUE && !is.null(param_loop_select) && length(param_loop_select) > 0) {
          new_loop_mod <- data.frame(
            text = param_loop_select,
            color = modify_params_loop_color,
            alpha = modify_params_loop_alpha,
            radius = modify_params_loop_radius,
            width = modify_params_loop_width,
            type = modify_params_loop_type,
            arrow_size = modify_params_loop_arrow_size, # arrow size
            gap_size = modify_params_loop_gap_size,
            two_way = modify_params_loop_two_way,
            stringsAsFactors = FALSE
          )

          modifications$loop <- rbind(modifications$loop, new_loop_mod)
        }

        if (modify_params_loop_xy == TRUE && !is.null(modify_params_loop_select_xy) && length(modify_params_loop_select_xy) > 0) {
          new_mod_label <- data.frame(
            text = modify_params_loop_select_xy,
            x_shift = modify_params_loop_shift_x,
            y_shift = modify_params_loop_shift_y,
            stringsAsFactors = FALSE
          )

          modifications$loop_xy <- rbind(
            modifications$loop_xy,
            new_mod_label)
        }

        if (modify_params_loop_location == TRUE && !is.null(modify_params_loop_select_location) && length(modify_params_loop_select_location) > 0) {
          new_mod_label <- data.frame(
            text = modify_params_loop_select_location,
            loop_location = modify_params_loop_location_value,
            stringsAsFactors = FALSE
          )

          modifications$loop_location <- rbind(
            modifications$loop_location,
            new_mod_label)
        }

        if (modify_params_looplabel == TRUE && !is.null(param_looplabel_select) && length(param_looplabel_select) > 0) {

          new_mod_label <- data.frame(
            text = param_looplabel_select,
            color = modify_params_looplabel_color,
            fill = modify_params_looplabel_fill,
            size = modify_params_looplabel_size,
            alpha = modify_params_looplabel_alpha,
            angle = modify_params_looplabel_angle,
            font = modify_params_looplabel_font,
            fontface = modify_params_looplabel_fontface,
            stringsAsFactors = FALSE
          )

          modifications$looplabel <- rbind(
            modifications$looplabel,
            new_mod_label
          )
        }

        if (modify_params_looplabel_xy == TRUE && !is.null(modify_params_looplabel_xy_select) && length(modify_params_looplabel_xy_select) > 0) {
          new_mod_label <- data.frame(
            text = modify_params_looplabel_xy_select,
            x_shift = modify_params_looplabel_shift_x,
            y_shift = modify_params_looplabel_shift_y,
            stringsAsFactors = FALSE
          )

          modifications$looplabel_xy <- rbind(
            modifications$looplabel_xy,
            new_mod_label
          )
        }

        if (modify_params_looplabel_text == TRUE && !is.null(params_looplabel_text) && length(params_looplabel_text) > 0) {
          new_loop_label_text <- data.frame(
            text = params_looplabel_text,
            looplabel = params_looplabel_looplabel,
            math_expression = params_looplabel_text_math_expression,
            stringsAsFactors = FALSE
          )

          modifications$looplabel_text <- rbind(
            modifications$looplabel_text,
            new_loop_label_text
          )
        }

        temp_mod <- values$group_storage$modifications
        temp_mod[[group_id]] <- modifications
        values$group_storage$modifications <- temp_mod

        highlight_multi_group <- input$highlight_multi_group

        p_val_alpha_compare <- input$p_val_alpha_compare %||%
          group_storage$last_p_val_alpha_compare %||%
          0.05

        min_rope <- input$min_rope %||%
          group_storage$last_min_rope %||%
          -0.1

        max_rope <- input$max_rope %||%
          group_storage$last_max_rope %||%
          0.1

        bayes_significance <- input$bayes_significance %||%
          group_storage$last_bayes_significance %||%
          'excludes_rope'

        sig_diff_edge <- NULL
        sig_diff_edgelabel <- NULL
        sig_diff_loop <- NULL
        sig_diff_looplabel <- NULL

        if (highlight_multi_group) {

          if (!multi_group_sem_combine_menu) {
            multi_group_first <- input$multi_group_first
            multi_group_second <- input$multi_group_second

            model_first <- values$group_storage$sem[[multi_group_first]]$current
            model_second <- values$group_storage$sem[[multi_group_second]]$current

            first_group_level <-  values$group_storage$sem[[multi_group_first]]$last_group_level
            second_group_level <- values$group_storage$sem[[multi_group_second]]$last_group_level

          } else {
            multi_group_first <- values$group_storage$sem[[group_id]]$first_group_id
            multi_group_second <- values$group_storage$sem[[group_id]]$second_group_id

            model_first <- values$group_storage$sem[[group_id]]$first_model
            model_second <- values$group_storage$sem[[group_id]]$second_model

            first_group_level <-  values$group_storage$sem[[group_id]]$first_group
            second_group_level <- values$group_storage$sem[[group_id]]$second_group

          }

          model_same <- identical(lavaan::parTable(model_first),
                                  lavaan::parTable(model_second))

          if (model_same) {

            if (is(model_first)[[1]] == 'blavaan') {

              res <- get_bayesian_group_differences(fit, rope = c(min_rope, max_rope),
                                                    group1 = first_group_level,
                                                    group2 = second_group_level)

              if (bayes_significance == "excludes_zero") {
                res$significant <- res$excludes_zero
              } else if (bayes_significance == "excludes_rope") {
                res$significant <- res$excludes_rope
              }

              res <- res[res$significant, ]

            } else if (is(model_first)[[1]] == 'lavaan') {

              res <- get_group_differences(model_first, alpha = p_val_alpha_compare,
                                           group1 = first_group_level,
                                           group2 = second_group_level)
            }

            sig_diff_results(res)

            if (nrow(res) == 0) showNotification("No significant paths are detected", type = "message")

            res_edge <- subset(res, lhs != rhs)
            res_loop <- subset(res, lhs == rhs)

            sig_diff_path_color <- input$sig_diff_path_color
            sig_diff_path_line_width <- input$sig_diff_path_line_width
            sig_diff_edgelabel_fontface <- input$sig_diff_edgelabel_fontface
            highlight_multi_group_edges <- input$highlight_multi_group_edges

            if (nrow(res_edge) > 0) {
              sig_diff_edge <- data.frame(
                lhs = res_edge$lhs,
                op = 'to',
                rhs = res_edge$rhs,
                color = sig_diff_path_color,
                width = sig_diff_path_line_width,
                stringsAsFactors = FALSE
              )

              sig_diff_edgelabel <- data.frame(
                lhs = res_edge$lhs,
                op = 'to',
                rhs = res_edge$rhs,
                color = sig_diff_path_color,
                fontface = sig_diff_edgelabel_fontface,
                stringsAsFactors = FALSE
              )

            }

            if (nrow(res_loop) > 0) {

              sig_diff_loop = data.frame(
                text = res_loop$lhs,
                color =  sig_diff_path_color,
                width = sig_diff_path_line_width,
                stringsAsFactors = FALSE
              )

              sig_diff_looplabel <- data.frame(
                text = res_loop$lhs,
                color = sig_diff_path_color,
                fontface = sig_diff_edgelabel_fontface,
                stringsAsFactors = FALSE
              )
            }

            values$group_storage$sem[[group_id]]$last_sig_diff_edge <- sig_diff_edge
            values$group_storage$sem[[group_id]]$last_sig_diff_edgelabel <- sig_diff_edgelabel
            values$group_storage$sem[[group_id]]$last_sig_diff_loop <- sig_diff_loop
            values$group_storage$sem[[group_id]]$last_sig_diff_looplabel <- sig_diff_looplabel
            values$group_storage$sem[[group_id]]$last_highlight_multi_group_edges <- highlight_multi_group_edges
          }
        }


        if (highlight_free_path) {
          params_status <- classify_parameters(group_storage$current, target_group = group_storage$last_group_level)

          params_free_edge <- subset(params_status, lhs != rhs & param_status == 'free')
          params_fixed_edge <- subset(params_status, lhs != rhs & param_status == 'fixed')
          params_free_loop <- subset(params_status, lhs == rhs & param_status == 'free')
          params_fixed_loop <- subset(params_status, lhs == rhs & param_status == 'fixed')

          if (!is.null(free_path_color)) values$group_storage$sem[[group_id]]$last_free_path_color <- free_path_color
          if (!is.null(fixed_path_color)) values$group_storage$sem[[group_id]]$last_fixed_path_color <- fixed_path_color
          if (!is.null(free_label_fontface)) values$group_storage$sem[[group_id]]$last_free_label_fontface <- free_label_fontface
          if (!is.null(fixed_label_fontface)) values$group_storage$sem[[group_id]]$last_fixed_label_fontface <- fixed_label_fontface

          if (nrow(params_free_edge) > 0) {
            highlight_free_edge <- data.frame(
              lhs = params_free_edge$lhs,
              op = 'to',
              rhs = params_free_edge$rhs,
              color = free_path_color,
              stringsAsFactors = FALSE
            )

            highlight_free_edgelabel <- data.frame(
              lhs = params_free_edge$lhs,
              op = 'to',
              rhs = params_free_edge$rhs,
              color = free_path_color,
              fontface = free_label_fontface,
              stringsAsFactors = FALSE
            )

          } else {
            highlight_free_edge <- data.frame(
              lhs = character(),
              op = character(),
              rhs = character(),
              color = character(),
              stringsAsFactors = FALSE
            )

            highlight_free_edgelabel <- data.frame(
              lhs = character(),
              op = character(),
              rhs = character(),
              color = character(),
              fontface = character(),
              stringsAsFactors = FALSE
            )
          }

          if (nrow(params_fixed_edge) > 0) {
            highlight_fixed_edge <- data.frame(
              lhs = params_fixed_edge$lhs,
              op = 'to',
              rhs = params_fixed_edge$rhs,
              color = fixed_path_color,
              stringsAsFactors = FALSE
            )

            highlight_fixed_edgelabel <- data.frame(
              lhs = params_fixed_edge$lhs,
              op = 'to',
              rhs = params_fixed_edge$rhs,
              color = fixed_path_color,
              fontface = fixed_label_fontface,
              stringsAsFactors = FALSE
            )
          } else {
            highlight_fixed_edge <- data.frame(
              lhs = character(),
              op = character(),
              rhs = character(),
              color = character(),
              stringsAsFactors = FALSE
            )

            highlight_fixed_edgelabel <- data.frame(
              lhs = character(),
              op = character(),
              rhs = character(),
              color = character(),
              fontface = character(),
              stringsAsFactors = FALSE
            )
          }

          if (nrow(params_free_loop) > 0) {
            highlight_free_loop <- data.frame(
              text = params_free_loop$lhs,
              color = free_path_color,
              stringsAsFactors = FALSE
            )

            highlight_free_looplabel <- data.frame(
              text = params_free_loop$lhs,
              color = free_path_color,
              fontface = free_label_fontface,
              stringsAsFactors = FALSE
            )
          } else {
            highlight_free_loop <- data.frame(
              text = character(),
              color = character(),
              stringsAsFactors = FALSE
            )

            highlight_free_looplabel <- data.frame(
              text = character(),
              color = character(),
              fontface = character(),
              stringsAsFactors = FALSE
            )
          }

          if (nrow(params_fixed_loop) > 0) {
            highlight_fixed_loop <- data.frame(
              text = params_fixed_loop$lhs,
              color = fixed_path_color,
              stringsAsFactors = FALSE
            )

            highlight_fixed_looplabel <- data.frame(
              text = params_fixed_loop$lhs,
              color = fixed_path_color,
              fontface = fixed_label_fontface,
              stringsAsFactors = FALSE
            )
          } else {
            highlight_fixed_loop <- data.frame(
              text = character(),
              color = character(),
              stringsAsFactors = FALSE
            )

            highlight_fixed_looplabel <- data.frame(
              text = character(),
              color = character(),
              fontface = character(),
              stringsAsFactors = FALSE
            )
          }


          ff_params_edge <- rbind(highlight_free_edge, highlight_fixed_edge)
          ff_params_loop <- rbind(highlight_free_loop, highlight_fixed_loop)
          ff_params_edgelabel <- rbind(highlight_free_edgelabel, highlight_fixed_edgelabel)
          ff_params_looplabel <- rbind(highlight_free_looplabel, highlight_fixed_looplabel)

        } else {
          ff_params_edge <- NULL
          ff_params_loop <- NULL
          ff_params_edgelabel <- NULL
          ff_params_looplabel <- NULL
        }

        highlight_free_path_multi_group <- input$highlight_free_path_multi_group
        highlight_free_path_multi_group_invariance <- input$highlight_free_path_multi_group_invariance
        highlight_free_path_multi_group_difference <- input$highlight_free_path_multi_group_difference

        highlight_fixed_equal_edge <- NULL
        highlight_free_varying_edge <- NULL
        highlight_fixed_equal_edgelabel <- NULL
        highlight_free_varying_edgelabel <- NULL
        highlight_free_varying_loop <- NULL
        highlight_fixed_varying_loop <- NULL
        highlight_free_varying_looplabel <- NULL
        highlight_fixed_varying_looplabel <- NULL

        ff_params_edge_multi <- NULL
        ff_params_edgelabel_multi <- NULL
        ff_params_loop_multi <- NULL
        ff_params_looplabel_multi <- NULL


        if (highlight_free_path_multi_group) {

          if (!multi_group_sem_combine_menu) {
            multi_group_first <- input$multi_group_first_free
            multi_group_second <- input$multi_group_second_free

            model_first <- values$group_storage$sem[[multi_group_first]]$current
            model_second <- values$group_storage$sem[[multi_group_second]]$current

            first_group_level <-  values$group_storage$sem[[multi_group_first]]$last_group_level
            second_group_level <- values$group_storage$sem[[multi_group_second]]$last_group_level

          } else {
            multi_group_first <- values$group_storage$sem[[group_id]]$first_group_id
            multi_group_second <- values$group_storage$sem[[group_id]]$second_group_id


            model_first <- values$group_storage$sem[[group_id]]$first_model
            model_second <- values$group_storage$sem[[group_id]]$second_model

            first_group_level <-  values$group_storage$sem[[group_id]]$first_group
            second_group_level <- values$group_storage$sem[[group_id]]$second_group

          }

          model_same <- identical(lavaan::parTable(model_first),
                                  lavaan::parTable(model_second))

          if (model_same) {
            params_status_multiple <- classify_parameters_across_groups(model_first)
            params_status_single <- subset(params_status_multiple, group_label == first_group_level)
            params_status_single(params_status_single)

          } else {
            model_first <- values$group_storage$sem[[multi_group_first]]$current
            params_status_single <- classify_parameters_across_groups(model_first, group_label == first_group_level)
            params_status_single(params_status_single)
          }

          # Edges
          fixed_equal_edge = subset(params_status_single, lhs != rhs & param_status == 'fixed_equal')
          free_varying_edge = subset(params_status_single, lhs != rhs & param_status == 'free_varying')

          # Loops
          fixed_equal_loop = subset(params_status_single, lhs == rhs & param_status == 'fixed_equal') # cross_group_constraint (estimated but fixed)
          free_varying_loop = subset(params_status_single, lhs == rhs & param_status == 'free_varying') # group_specific


          if (nrow(fixed_equal_edge) > 0) {

            highlight_fixed_equal_edge_color <- input$invariance_color %||% values$group_storage$sem[[group_id]]$last_invariance_color
            highlight_fixed_equal_edge_line_width <- input$invariance_line_width %||% values$group_storage$sem[[group_id]]$last_invariance_line_width

            highlight_fixed_equal_edge <- data.frame(
              lhs = fixed_equal_edge$lhs,
              op = 'to',
              rhs = fixed_equal_edge$rhs,
              color = highlight_fixed_equal_edge_color,
              width = highlight_fixed_equal_edge_line_width,
              stringsAsFactors = FALSE
            )

            highlight_fixed_equal_edgelabel_color <- input$invariance_color  %||% values$group_storage$sem[[group_id]]$last_invariance_color
            highlight_fixed_equal_edgelabel_fontface <- input$invariance_fontface %||% values$group_storage$sem[[group_id]]$last_invariance_fontface

            highlight_fixed_equal_edgelabel <- data.frame(
              lhs = fixed_equal_edge$lhs,
              op = 'to',
              rhs = fixed_equal_edge$rhs,
              color = highlight_fixed_equal_edgelabel_color,
              fontface = highlight_fixed_equal_edgelabel_fontface,
              stringsAsFactors = FALSE
            )

          }

          if (nrow(free_varying_edge) > 0) {

            highlight_free_varying_edge_color <- input$group_diff_color %||% values$group_storage$sem[[group_id]]$last_group_diff_color
            highlight_free_varying_edge_line_width <- input$group_diff_line_width %||% values$group_storage$sem[[group_id]]$last_group_diff_line_width

            highlight_free_varying_edge <- data.frame(
              lhs = free_varying_edge$lhs,
              op = 'to',
              rhs = free_varying_edge$rhs,
              color = highlight_free_varying_edge_color,
              width = highlight_free_varying_edge_line_width,
              stringsAsFactors = FALSE
            )

            highlight_free_varying_edgelabel_color <- input$group_diff_color %||% values$group_storage$sem[[group_id]]$last_group_diff_color
            highlight_free_varying_edgelabel_fontface <- input$group_diff_fontface %||% values$group_storage$sem[[group_id]]$last_group_diff_fontface

            highlight_free_varying_edgelabel <- data.frame(
              lhs = free_varying_edge$lhs,
              op = 'to',
              rhs = free_varying_edge$rhs,
              color = highlight_free_varying_edgelabel_color,
              fontface = highlight_free_varying_edgelabel_fontface,
              stringsAsFactors = FALSE
            )
          }

          # FIXED EQUAL LOOPS
          if (nrow(fixed_equal_loop) > 0) {

            highlight_fixed_equal_loop_color <- input$invariance_color %||% values$group_storage$sem[[group_id]]$last_invariance_color
            highlight_fixed_equal_loop_line_width <- input$invariance_line_width %||% values$group_storage$sem[[group_id]]$last_invariance_line_width


            highlight_fixed_equal_loop <- data.frame(
              text = fixed_equal_loop$lhs,  # For loops, lhs == rhs, so use either
              color = highlight_fixed_equal_loop_color,
              width = highlight_fixed_equal_loop_line_width,
              stringsAsFactors = FALSE
            )

            highlight_fixed_equal_looplabel_color <- input$invariance_color %||% values$group_storage$sem[[group_id]]$last_invariance_color
            highlight_fixed_equal_looplabel_fontface <- input$invariance_fontface %||% values$group_storage$sem[[group_id]]$last_invariance_fontface


            highlight_fixed_equal_looplabel <- data.frame(
              text = fixed_equal_loop$lhs,
              color = highlight_fixed_equal_looplabel_color,
              fontface = highlight_fixed_equal_looplabel_fontface,
              stringsAsFactors = FALSE
            )
          }

          # FREE VARYING LOOPS
          if (nrow(free_varying_loop) > 0) {

            highlight_free_varying_loop_color <- input$group_diff_color %||% values$group_storage$sem[[group_id]]$last_group_diff_color
            highlight_free_varying_loop_line_width <- input$group_diff_line_width %||% values$group_storage$sem[[group_id]]$last_group_diff_line_width

            highlight_free_varying_loop <- data.frame(
              text = free_varying_loop$lhs,  # For loops, lhs == rhs
              color = highlight_free_varying_loop_color,
              width = highlight_free_varying_loop_line_width,
              stringsAsFactors = FALSE
            )

            highlight_free_varying_looplabel_color <- input$group_diff_color %||% values$group_storage$sem[[group_id]]$last_group_diff_color
            highlight_free_varying_looplabel_fontface <- input$group_diff_fontface %||% values$group_storage$sem[[group_id]]$last_group_diff_fontface

            highlight_free_varying_looplabel <- data.frame(
              text = free_varying_loop$lhs,
              color = highlight_free_varying_looplabel_color,
              fontface = highlight_free_varying_looplabel_fontface,
              stringsAsFactors = FALSE
            )
          }

          if (!is.null(input$invariance_color)) values$group_storage$sem[[group_id]]$last_invariance_color <- input$invariance_color
          if (!is.null(input$invariance_line_width)) values$group_storage$sem[[group_id]]$last_invariance_line_width <- input$invariance_line_width
          if (!is.null(input$invariance_fontface)) values$group_storage$sem[[group_id]]$last_invariance_fontface <- input$invariance_fontface

          if (!is.null(input$group_diff_color)) values$group_storage$sem[[group_id]]$last_group_diff_color <- input$group_diff_color
          if (!is.null(input$group_diff_line_width)) values$group_storage$sem[[group_id]]$last_group_diff_line_width <- input$group_diff_line_width
          if (!is.null(input$group_diff_fontface)) values$group_storage$sem[[group_id]]$last_group_diff_fontface <- input$group_diff_fontface

          ff_params_edge_multi <- rbind(highlight_fixed_equal_edge, highlight_free_varying_edge)
          ff_params_edgelabel_multi <- rbind(highlight_fixed_equal_edgelabel, highlight_free_varying_edgelabel)
          ff_params_loop_multi <- rbind(highlight_free_varying_loop, highlight_fixed_varying_loop)
          ff_params_looplabel_multi <- rbind(highlight_free_varying_looplabel, highlight_fixed_varying_looplabel)

        }

        values$group_storage$sem[[group_id]]$last_highlight_multi_group <- highlight_multi_group

        values$group_storage$sem[[group_id]]$last_bayes_significance <- bayes_significance
        values$group_storage$sem[[group_id]]$last_p_val_alpha_compare <- p_val_alpha_compare
        values$group_storage$sem[[group_id]]$last_min_rope <- min_rope
        values$group_storage$sem[[group_id]]$last_max_rope <- max_rope

        values$group_storage$sem[[group_id]]$last_highlight_free_path <- highlight_free_path
        values$group_storage$sem[[group_id]]$last_ff_params_edge <- ff_params_edge
        values$group_storage$sem[[group_id]]$last_ff_params_loop <- ff_params_loop
        values$group_storage$sem[[group_id]]$last_ff_params_edgelabel <- ff_params_edgelabel
        values$group_storage$sem[[group_id]]$last_ff_params_looplabel <- ff_params_looplabel

        values$group_storage$sem[[group_id]]$last_highlight_free_path_multi_group <- highlight_free_path_multi_group
        values$group_storage$sem[[group_id]]$last_highlight_free_path_multi_group_invariance <- highlight_free_path_multi_group_invariance
        values$group_storage$sem[[group_id]]$last_highlight_free_path_multi_group_difference <- highlight_free_path_multi_group_difference

        values$group_storage$sem[[group_id]]$last_ff_params_edge_multi <- ff_params_edge_multi
        values$group_storage$sem[[group_id]]$last_ff_params_edgelabel_multi <- ff_params_edgelabel_multi
        values$group_storage$sem[[group_id]]$last_ff_params_loop_multi <- ff_params_loop_multi
        values$group_storage$sem[[group_id]]$last_ff_params_looplabel_multi <- ff_params_looplabel_multi

        if (inherits(bundleObject, c("grViz"))) {

          graph_data <- generate_graph_from_diagrammeR(grviz_object,
                                                       relative_x_position = relative_x_position,
                                                       relative_y_position = relative_y_position,
                                                       center_x = center_x_position,
                                                       center_y = center_y_position,
                                                       latent_shape = latent_shape,
                                                       observed_shape = observed_shape,
                                                       int_shape = int_shape,
                                                       point_size_latent = latent_size_input,
                                                       point_size_observed = observed_size_input,
                                                       point_size_int = int_size_input,
                                                       width_height_ratio_latent = width_height_ratio_latent,
                                                       width_height_ratio_observed = width_height_ratio_observed,
                                                       width_height_ratio_int = width_height_ratio_int,
                                                       line_width = line_width,
                                                       line_alpha = line_alpha,
                                                       text_size_latent = text_size_latent,
                                                       text_font_latent = text_font_latent,
                                                       text_color_latent = text_color_latent,
                                                       text_alpha_latent = text_alpha_latent,
                                                       text_fontface_latent = text_fontface_latent,
                                                       text_size_others = text_size_others,
                                                       text_font_others = text_font_others,
                                                       text_color_others = text_color_others,
                                                       text_alpha_others = text_alpha_others,
                                                       text_fontface_others = text_fontface_others,
                                                       text_size_edges = text_size_edges,
                                                       text_font_edges = text_font_edges,
                                                       text_color_edges = text_color_edges,
                                                       text_color_fill = text_color_fill,
                                                       text_alpha_edges = text_alpha_edges,
                                                       text_fontface_edges = text_fontface_edges,
                                                       point_color_latent = latent_color_input,
                                                       point_color_observed = observed_color_input,
                                                       point_color_int = int_color_input,
                                                       edge_color = edge_color,
                                                       line_endpoint_spacing = line_endpoint_spacing,
                                                       node_border_color = node_border_color,
                                                       node_border_width = node_border_width,
                                                       arrow_type = lavaan_arrow_type,
                                                       arrow_size = lavaan_arrow_size,
                                                       lavaan_arrow_location = lavaan_arrow_location,
                                                       zoom_factor = input$zoom,
                                                       lavaan_curvature_magnitude = lavaan_curvature_magnitude,
                                                       lavaan_rotate_curvature = lavaan_rotate_curvature,
                                                       lavaan_curvature_asymmetry = lavaan_curvature_asymmetry,
                                                       lavaan_curved_x_shift = lavaan_curved_x_shift,
                                                       lavaan_curved_y_shift = lavaan_curved_y_shift,
                                                       remove_edgelabels = remove_edgelabels,
                                                       highlight_free_path = highlight_free_path,
                                                       ff_params_edge = ff_params_edge,
                                                       ff_params_edgelabel = ff_params_edgelabel,
                                                       highlight_sig_path = highlight_sig_path,
                                                       sig_path_color = sig_path_color,
                                                       non_sig_path_color = non_sig_path_color,
                                                       sig_label_fontface = sig_label_fontface,
                                                       non_sig_label_fontface = non_sig_label_fontface,
                                                       data_file = group_storage$data_file,
                                                       modify_params_edge = ifelse (!is.null(modifications$edge), TRUE, FALSE),
                                                       modified_edges = modifications$edge,
                                                       modify_params_edgelabel = ifelse (!is.null(modifications$edgelabel), TRUE, FALSE),
                                                       modified_edgelabels = modifications$edgelabel,
                                                       modify_params_edgelabel_xy = ifelse (!is.null(modifications$edgelabel_xy), TRUE, FALSE),
                                                       modified_edgelabels_xy = modifications$edgelabel_xy,
                                                       modify_params_edgelabel_text = ifelse (!is.null(modifications$edgelabel_text), TRUE, FALSE),
                                                       modified_edgelabels_text = modifications$edgelabel_text,
                                                       modify_params_node = ifelse (!is.null(modifications$node), TRUE, FALSE),
                                                       modified_nodes = modifications$node,
                                                       modify_params_node_xy = ifelse (!is.null(modifications$node_xy), TRUE, FALSE),
                                                       modified_nodes_xy = modifications$node_xy,
                                                       modify_params_edge_xy = ifelse (!is.null(modifications$edge_xy), TRUE, FALSE),
                                                       modified_edges_xy = modifications$edge_xy,
                                                       modify_params_cov_edge = ifelse (!is.null(modifications$cov_edge), TRUE, FALSE),
                                                       modified_cov_edges = modifications$cov_edge,
                                                       modify_params_nodelabel = ifelse (!is.null(modifications$nodelabel), TRUE, FALSE),
                                                       modified_nodelabels = modifications$nodelabel,
                                                       modify_params_nodelabel_xy = ifelse (!is.null(modifications$nodelabel_xy), TRUE, FALSE),
                                                       modified_nodelabels_xy = modifications$nodelabel_xy,
                                                       modify_params_nodelabel_text = ifelse (!is.null(modifications$nodelabel_text), TRUE, FALSE),
                                                       modified_nodelabels_text = modifications$nodelabel_text,
                                                       modify_params_latent_node_xy = ifelse (!is.null(modifications$latent_node_xy), TRUE, FALSE),
                                                       modified_latent_nodes_xy = modifications$latent_node_xy,
                                                       modify_params_latent_node_angle = ifelse (!is.null(modifications$latent_node_angle), TRUE, FALSE),
                                                       modified_latent_nodes_angle = modifications$latent_node_angle,
                                                       apply_global_nodes = TRUE,
                                                       apply_global_edges = TRUE,
                                                       apply_global_annotations = TRUE,
                                                       flip_layout = flip_sem_layout,
                                                       flip_direction = flip_sem_layout_direction,
                                                       rotate_layout = rotate_sem_layout,
                                                       rotate_angle = rotate_sem_layout_angle,
                                                       which_group = group_id
          )
        } else if (inherits(bundleObject, c("sem_graph"))) {

          graph_data <- generate_graph_from_tidySEM(sem_paths, fit_delta,
                                                    relative_x_position = relative_x_position,
                                                    relative_y_position = relative_y_position,
                                                    center_x = center_x_position,
                                                    center_y = center_y_position,
                                                    latent_shape = latent_shape,
                                                    observed_shape = observed_shape,
                                                    int_shape = int_shape,
                                                    point_size_latent = latent_size_input,
                                                    point_size_observed = observed_size_input,
                                                    point_size_int = int_size_input,
                                                    width_height_ratio_latent = width_height_ratio_latent,
                                                    width_height_ratio_observed = width_height_ratio_observed,
                                                    width_height_ratio_int = width_height_ratio_int,
                                                    line_width = line_width,
                                                    line_alpha = line_alpha,
                                                    text_size_latent = text_size_latent,
                                                    text_font_latent = text_font_latent,
                                                    text_color_latent = text_color_latent,
                                                    text_alpha_latent = text_alpha_latent,
                                                    text_fontface_latent = text_fontface_latent,
                                                    text_size_others = text_size_others,
                                                    text_font_others = text_font_others,
                                                    text_color_others = text_color_others,
                                                    text_alpha_others = text_alpha_others,
                                                    text_fontface_others = text_fontface_others,
                                                    text_size_edges = text_size_edges,
                                                    text_font_edges = text_font_edges,
                                                    text_color_edges = text_color_edges,
                                                    text_color_fill = text_color_fill,
                                                    text_alpha_edges = text_alpha_edges,
                                                    text_fontface_edges = text_fontface_edges,
                                                    point_color_latent = latent_color_input,
                                                    point_color_observed = observed_color_input,
                                                    point_color_int = int_color_input,
                                                    edge_color = edge_color,
                                                    line_endpoint_spacing = line_endpoint_spacing,
                                                    node_border_color = node_border_color,
                                                    node_border_width = node_border_width,
                                                    arrow_type = lavaan_arrow_type,
                                                    arrow_size = lavaan_arrow_size,
                                                    lavaan_arrow_location = lavaan_arrow_location,
                                                    zoom_factor = input$zoom,
                                                    lavaan_curvature_magnitude = lavaan_curvature_magnitude,
                                                    lavaan_rotate_curvature = lavaan_rotate_curvature,
                                                    lavaan_curvature_asymmetry = lavaan_curvature_asymmetry,
                                                    lavaan_curved_x_shift = lavaan_curved_x_shift,
                                                    lavaan_curved_y_shift = lavaan_curved_y_shift,
                                                    remove_edgelabels = remove_edgelabels,
                                                    highlight_free_path = highlight_free_path,
                                                    ff_params_edge = ff_params_edge,
                                                    ff_params_edgelabel = ff_params_edgelabel,
                                                    ff_params_loop = ff_params_loop,
                                                    ff_params_looplabel = ff_params_looplabel,
                                                    highlight_free_path_multi_group = highlight_free_path_multi_group,
                                                    ff_params_edge_multi = ff_params_edge_multi,
                                                    ff_params_edgelabel_multi = ff_params_edgelabel_multi,
                                                    ff_params_loop_multi = ff_params_loop_multi,
                                                    ff_params_looplabel_multi = ff_params_looplabel_multi,
                                                    highlight_sig_path = highlight_sig_path,
                                                    sig_path_color = sig_path_color,
                                                    non_sig_path_color = non_sig_path_color,
                                                    sig_label_fontface = sig_label_fontface,
                                                    non_sig_label_fontface = non_sig_label_fontface,
                                                    highlight_multi_group = highlight_multi_group,
                                                    sig_diff_edge = sig_diff_edge,
                                                    sig_diff_edgelabel = sig_diff_edgelabel,
                                                    sig_diff_loop = sig_diff_loop,
                                                    sig_diff_looplabel = sig_diff_looplabel,
                                                    residuals = residuals,
                                                    residuals_orientation_type = residuals_orientation_type,
                                                    lavaan_loop_offset = lavaan_loop_offset,
                                                    lavaan_radius = lavaan_radius,
                                                    lavaan_line_color_loop = lavaan_line_color_loop,
                                                    lavaan_line_alpha_loop = lavaan_line_alpha_loop,
                                                    lavaan_arrow_type_loop = lavaan_arrow_type_loop,
                                                    lavaan_arrow_size_loop = lavaan_arrow_size_loop,
                                                    lavaan_width_loop = lavaan_width_loop,
                                                    lavaan_height_loop = lavaan_height_loop,
                                                    lavaan_gap_size_loop = lavaan_gap_size_loop,
                                                    lavaan_two_way_arrow_loop = lavaan_two_way_arrow_loop,
                                                    data_file = group_storage$data_file,
                                                    modify_params_edge = ifelse (!is.null(modifications$edge), TRUE, FALSE),
                                                    modified_edges = modifications$edge,
                                                    modify_params_edgelabel = ifelse (!is.null(modifications$edgelabel), TRUE, FALSE),
                                                    modified_edgelabels = modifications$edgelabel,
                                                    modify_params_edgelabel_xy = ifelse (!is.null(modifications$edgelabel_xy), TRUE, FALSE),
                                                    modified_edgelabels_xy = modifications$edgelabel_xy,
                                                    modify_params_edgelabel_text = ifelse (!is.null(modifications$edgelabel_text), TRUE, FALSE),
                                                    modified_edgelabels_text = modifications$edgelabel_text,
                                                    modify_params_node = ifelse (!is.null(modifications$node), TRUE, FALSE),
                                                    modified_nodes = modifications$node,
                                                    modify_params_node_xy = ifelse (!is.null(modifications$node_xy), TRUE, FALSE),
                                                    modified_nodes_xy = modifications$node_xy,
                                                    modify_params_edge_xy = ifelse (!is.null(modifications$edge_xy), TRUE, FALSE),
                                                    modified_edges_xy = modifications$edge_xy,
                                                    modify_params_cov_edge = ifelse (!is.null(modifications$cov_edge), TRUE, FALSE),
                                                    modified_cov_edges = modifications$cov_edge,
                                                    modify_params_nodelabel = ifelse (!is.null(modifications$nodelabel), TRUE, FALSE),
                                                    modified_nodelabels = modifications$nodelabel,
                                                    modify_params_nodelabel_xy = ifelse (!is.null(modifications$nodelabel_xy), TRUE, FALSE),
                                                    modified_nodelabels_xy = modifications$nodelabel_xy,
                                                    modify_params_nodelabel_text = ifelse (!is.null(modifications$nodelabel_text), TRUE, FALSE),
                                                    modified_nodelabels_text = modifications$nodelabel_text,
                                                    modify_params_latent_node_xy = ifelse (!is.null(modifications$latent_node_xy), TRUE, FALSE),
                                                    modified_latent_nodes_xy = modifications$latent_node_xy,
                                                    modify_params_latent_node_angle = ifelse (!is.null(modifications$latent_node_angle), TRUE, FALSE),
                                                    modified_latent_nodes_angle = modifications$latent_node_angle,
                                                    modify_params_loop = ifelse (!is.null(modifications$loop), TRUE, FALSE),
                                                    modified_loops = modifications$loop,
                                                    modify_params_loop_xy = ifelse (!is.null(modifications$loop_xy), TRUE, FALSE),
                                                    modified_loops_xy = modifications$loop_xy,
                                                    modify_params_loop_location = ifelse (!is.null(modifications$loop_location), TRUE, FALSE),
                                                    modified_loops_location = modifications$loop_location,
                                                    modify_params_looplabel = ifelse (!is.null(modifications$looplabel), TRUE, FALSE),
                                                    modified_looplabels = modifications$looplabel,
                                                    modify_params_looplabel_xy = ifelse (!is.null(modifications$looplabel_xy), TRUE, FALSE),
                                                    modified_looplabels_xy = modifications$looplabel_xy,
                                                    modify_params_looplabel_text = ifelse (!is.null(modifications$looplabel_text), TRUE, FALSE),
                                                    modified_looplabels_text = modifications$looplabel_text,
                                                    loop_names_remove = loop_names_remove,
                                                    flip_layout = flip_sem_layout,
                                                    flip_direction = flip_sem_layout_direction,
                                                    rotate_layout = rotate_sem_layout,
                                                    rotate_angle = rotate_sem_layout_angle,
                                                    which_group = group_id,
                                                    group_level = group_level
          )

        } else {

          if (group_storage$last_lavaan_layout == 'layout_ai' && !layout_changed) {
            node_coords <- group_storage$node_coords0 # layout by AI
            node_coords0 <- node_coords
            node_coords$x <- (node_coords$x - mean(range(node_coords$x))) * relative_x_position + center_x_position
            node_coords$y <- (node_coords$y - mean(range(node_coords$y))) * relative_y_position + center_y_position
          } else if (group_storage$last_lavaan_layout != 'layout_ai' && lavaan_layout == 'layout_ai') {

            node_names <- names(sem_paths$graphAttributes$Nodes$labels)
            if (is.null(node_names)) node_names <- sem_paths$graphAttributes$Nodes$labels
            node_coords0 <- as.data.frame(sem_paths$layout)
            colnames(node_coords0) <- c("x", "y")
            node_coords0$name <- node_names

            if (input$ai_model %in% c("gemini", "openai", "mistral", "claude")) {
              api_key <- switch(
                input$ai_model,
                "gemini" = input$gemini_api_key,
                "openai" = input$openai_api_key,
                "mistral" = input$mistral_api_key,
                "claude" = input$claude_api_key
              )

              tryCatch({
                validate_api_key(api_key, input$ai_model)
              },
              error = function(e) {
                showNotification(
                  paste("API key validation failed for", input$ai_model, ":", e$message),
                  type = "error"
                )
                return(NULL)
              })
            }

            ai_settings <- list(
              gemini = input$gemini_api_key,
              openai = input$openai_api_key,
              mistral = input$mistral_api_key,
              claude = input$claude_api_key,
              ollama_model = input$ollama_model
            )
            showModal(modalDialog(
              title = paste("Generating SEM layout with", input$ai_model, "..."),
              "Please wait while AI generates SEM layout",
              footer = NULL,
              easyClose = FALSE
            ))

            prompt <- create_layout_prompt(node_coords0, comments = input$additional_prompts_layout)
            ai_response <- tryCatch({
              call_selected_ai(prompt, input$ai_model, ai_settings)
            }, error = function(e) {
              removeModal()
              showNotification(
                paste("Error calling AI model:", e$message),
                type = "error"
              )
              return(NULL)
            })

            if (!is.null(ai_response)) {
              node_coords0 <- tryCatch(
                {
                  parse_ai_layout_response(ai_response)
                },
                error = function(e) {
                  showNotification(
                    paste("Error parsing AI response:", e$message),
                    type = "error"
                  )
                  return(node_coords0) # keep old coords if parsing fails
                }
              )

              removeModal()
              showNotification("Lavaan layout generated successfully with GenAI!", type = "message")
            }

            node_coords <- node_coords0 # template

            node_coords$x <- (node_coords$x - mean(range(node_coords$x))) * relative_x_position + center_x_position
            node_coords$y <- (node_coords$y - mean(range(node_coords$y))) * relative_y_position + center_y_position

          } else {
            node_coords <- generate_sem_node_coords(sem_paths, relative_x_position = relative_x_position,
                                                    relative_y_position = relative_y_position,
                                                    center_x = center_x_position,
                                                    center_y = center_y_position,
                                                    flip_layout = flip_sem_layout,
                                                    flip_direction = flip_sem_layout_direction,
                                                    rotate_layout = rotate_sem_layout,
                                                    rotate_angle = rotate_sem_layout_angle)
          }

          graph_data <- generate_graph_from_sempaths(sem_paths, node_coords,
                                                     latent_shape = latent_shape,
                                                     observed_shape = observed_shape,
                                                     int_shape = int_shape,
                                                     point_size_latent = latent_size_input,
                                                     point_size_observed = observed_size_input,
                                                     point_size_int = int_size_input,
                                                     width_height_ratio_latent = width_height_ratio_latent,
                                                     width_height_ratio_observed = width_height_ratio_observed,
                                                     width_height_ratio_int = width_height_ratio_int,
                                                     line_width = line_width,
                                                     line_alpha = line_alpha,
                                                     text_size_latent = text_size_latent,
                                                     text_font_latent = text_font_latent,
                                                     text_color_latent = text_color_latent,
                                                     text_alpha_latent = text_alpha_latent,
                                                     text_fontface_latent = text_fontface_latent,
                                                     text_size_others = text_size_others,
                                                     text_font_others = text_font_others,
                                                     text_color_others = text_color_others,
                                                     text_alpha_others = text_alpha_others,
                                                     text_fontface_others = text_fontface_others,
                                                     text_size_edges = text_size_edges,
                                                     text_font_edges = text_font_edges,
                                                     text_color_edges = text_color_edges,
                                                     text_color_fill = text_color_fill,
                                                     text_alpha_edges = text_alpha_edges,
                                                     text_fontface_edges = text_fontface_edges,
                                                     point_color_latent = latent_color_input,
                                                     point_color_observed = observed_color_input,
                                                     point_color_int = int_color_input,
                                                     edge_color = edge_color,
                                                     line_endpoint_spacing = line_endpoint_spacing,
                                                     node_border_color = node_border_color,
                                                     node_border_width = node_border_width,
                                                     arrow_type = lavaan_arrow_type,
                                                     arrow_size = lavaan_arrow_size,
                                                     lavaan_arrow_location = lavaan_arrow_location,
                                                     zoom_factor = input$zoom,
                                                     lavaan_curvature_magnitude = lavaan_curvature_magnitude,
                                                     lavaan_rotate_curvature = lavaan_rotate_curvature,
                                                     lavaan_curvature_asymmetry = lavaan_curvature_asymmetry,
                                                     lavaan_curved_x_shift = lavaan_curved_x_shift,
                                                     lavaan_curved_y_shift = lavaan_curved_y_shift,
                                                     remove_edgelabels = remove_edgelabels,
                                                     highlight_free_path = highlight_free_path,
                                                     ff_params_edge = ff_params_edge,
                                                     ff_params_edgelabel = ff_params_edgelabel,
                                                     ff_params_loop = ff_params_loop,
                                                     ff_params_looplabel = ff_params_looplabel,
                                                     highlight_free_path_multi_group = highlight_free_path_multi_group,
                                                     ff_params_edge_multi = ff_params_edge_multi,
                                                     ff_params_edgelabel_multi = ff_params_edgelabel_multi,
                                                     ff_params_loop_multi = ff_params_loop_multi,
                                                     ff_params_looplabel_multi = ff_params_looplabel_multi,
                                                     highlight_sig_path = highlight_sig_path,
                                                     sig_path_color = sig_path_color,
                                                     non_sig_path_color = non_sig_path_color,
                                                     sig_label_fontface = sig_label_fontface,
                                                     non_sig_label_fontface = non_sig_label_fontface,
                                                     highlight_multi_group = highlight_multi_group,
                                                     sig_diff_edge = sig_diff_edge,
                                                     sig_diff_edgelabel = sig_diff_edgelabel,
                                                     sig_diff_loop = sig_diff_loop,
                                                     sig_diff_looplabel = sig_diff_looplabel,
                                                     residuals = residuals,
                                                     residuals_orientation_type = residuals_orientation_type,
                                                     lavaan_loop_offset = lavaan_loop_offset,
                                                     lavaan_radius = lavaan_radius,
                                                     lavaan_line_color_loop = lavaan_line_color_loop,
                                                     lavaan_line_alpha_loop = lavaan_line_alpha_loop,
                                                     lavaan_arrow_type_loop = lavaan_arrow_type_loop,
                                                     lavaan_arrow_size_loop = lavaan_arrow_size_loop,
                                                     lavaan_width_loop = lavaan_width_loop,
                                                     lavaan_height_loop = lavaan_height_loop,
                                                     lavaan_gap_size_loop = lavaan_gap_size_loop,
                                                     lavaan_two_way_arrow_loop = lavaan_two_way_arrow_loop,
                                                     data_file = group_storage$data_file,
                                                     modify_params_edge = ifelse (!is.null(modifications$edge), TRUE, FALSE),
                                                     modified_edges = modifications$edge,
                                                     modify_params_edgelabel = ifelse (!is.null(modifications$edgelabel), TRUE, FALSE),
                                                     modified_edgelabels = modifications$edgelabel,
                                                     modify_params_edgelabel_xy = ifelse (!is.null(modifications$edgelabel_xy), TRUE, FALSE),
                                                     modified_edgelabels_xy = modifications$edgelabel_xy,
                                                     modify_params_edgelabel_text = ifelse (!is.null(modifications$edgelabel_text), TRUE, FALSE),
                                                     modified_edgelabels_text = modifications$edgelabel_text,
                                                     modify_params_node = ifelse (!is.null(modifications$node), TRUE, FALSE),
                                                     modified_nodes = modifications$node,
                                                     modify_params_node_xy = ifelse (!is.null(modifications$node_xy), TRUE, FALSE),
                                                     modified_nodes_xy = modifications$node_xy,
                                                     modify_params_edge_xy = ifelse (!is.null(modifications$edge_xy), TRUE, FALSE),
                                                     modified_edges_xy = modifications$edge_xy,
                                                     modify_params_cov_edge = ifelse (!is.null(modifications$cov_edge), TRUE, FALSE),
                                                     modified_cov_edges = modifications$cov_edge,
                                                     modify_params_nodelabel = ifelse (!is.null(modifications$nodelabel), TRUE, FALSE),
                                                     modified_nodelabels = modifications$nodelabel,
                                                     modify_params_nodelabel_xy = ifelse (!is.null(modifications$nodelabel_xy), TRUE, FALSE),
                                                     modified_nodelabels_xy = modifications$nodelabel_xy,
                                                     modify_params_nodelabel_text = ifelse (!is.null(modifications$nodelabel_text), TRUE, FALSE),
                                                     modified_nodelabels_text = modifications$nodelabel_text,
                                                     modify_params_latent_node_xy = ifelse (!is.null(modifications$latent_node_xy), TRUE, FALSE),
                                                     modified_latent_nodes_xy = modifications$latent_node_xy,
                                                     modify_params_latent_node_angle = ifelse (!is.null(modifications$latent_node_angle), TRUE, FALSE),
                                                     modified_latent_nodes_angle = modifications$latent_node_angle,
                                                     modify_params_loop = ifelse (!is.null(modifications$loop), TRUE, FALSE),
                                                     modified_loops = modifications$loop,
                                                     modify_params_loop_xy = ifelse (!is.null(modifications$loop_xy), TRUE, FALSE),
                                                     modified_loops_xy = modifications$loop_xy,
                                                     modify_params_loop_location = ifelse (!is.null(modifications$loop_location), TRUE, FALSE),
                                                     modified_loops_location = modifications$loop_location,
                                                     modify_params_looplabel = ifelse (!is.null(modifications$looplabel), TRUE, FALSE),
                                                     modified_looplabels = modifications$looplabel,
                                                     modify_params_looplabel_xy = ifelse (!is.null(modifications$looplabel_xy), TRUE, FALSE),
                                                     modified_looplabels_xy = modifications$looplabel_xy,
                                                     modify_params_looplabel_text = ifelse (!is.null(modifications$looplabel_text), TRUE, FALSE),
                                                     modified_looplabels_text = modifications$looplabel_text,
                                                     loop_names_remove = loop_names_remove,
                                                     which_group = group_id
          )
        }

        if (layout_changed) values$group_storage$sem[[group_id]]$last_lavaan_layout <- lavaan_layout
        if (lavaan_layout == 'custom') values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix0 <- lavaan_layout_matrix0
        values$group_storage$sem[[group_id]]$last_lavaan_layout_matrix <- lavaan_layout_matrix

        if (lavaan_layout == 'layout_ai') values$group_storage$sem[[group_id]]$node_coords0 <- node_coords0 # keep the template for AI layout, not used for others

        values$group_storage$sem[[group_id]]$intercepts <- intercepts

        values$group_storage$sem[[group_id]]$last_modify_params_node <- modify_params_node
        if (modify_params_node == TRUE && !is.null(param_node_select) && length(param_node_select) > 0) {
          values$group_storage$sem[[group_id]]$last_param_node_select <- param_node_select
          values$group_storage$sem[[group_id]]$last_modify_params_node_color <- modify_params_node_color
          values$group_storage$sem[[group_id]]$last_modify_params_node_alpha <- modify_params_node_alpha
          values$group_storage$sem[[group_id]]$last_modify_params_node_shape <- modify_params_node_shape
          values$group_storage$sem[[group_id]]$last_modify_params_node_size <- modify_params_node_size
          values$group_storage$sem[[group_id]]$last_modify_params_node_border_color <- modify_params_node_border_color
          values$group_storage$sem[[group_id]]$last_modify_params_node_border_width <- modify_params_node_border_width
          values$group_storage$sem[[group_id]]$last_modify_params_node_width_height_ratio <- modify_params_node_width_height_ratio
        }

        values$group_storage$sem[[group_id]]$last_modify_params_edge <- modify_params_edge
        if (modify_params_edge == TRUE && !is.null(param_edge_select) && length(param_edge_select) > 0) {
          values$group_storage$sem[[group_id]]$last_param_edge_select <- param_edge_select
          values$group_storage$sem[[group_id]]$last_modify_params_edge_color <- modify_params_edge_color
          values$group_storage$sem[[group_id]]$last_modify_params_edge_linewidth <- modify_params_edge_linewidth
          values$group_storage$sem[[group_id]]$last_modify_params_edge_alpha <- modify_params_edge_alpha
          values$group_storage$sem[[group_id]]$last_modify_params_edge_line_style <- modify_params_edge_line_style
          values$group_storage$sem[[group_id]]$last_modify_params_edge_color_type <- modify_params_edge_color_type
          values$group_storage$sem[[group_id]]$last_modify_params_edge_gradient_position <- modify_params_edge_gradient_position
          values$group_storage$sem[[group_id]]$last_modify_params_edge_end_color <- modify_params_edge_end_color
        }

        values$group_storage$sem[[group_id]]$last_lavaan_arrow_location <- lavaan_arrow_location

        values$group_storage$sem[[group_id]]$last_modify_params_edgelabel <- modify_params_edgelabel
        if (modify_params_edgelabel == TRUE && !is.null(param_edgelabel_select) && length(param_edgelabel_select) > 0) {
          values$group_storage$sem[[group_id]]$last_param_edgelabel_select <- param_edgelabel_select
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_color <- modify_params_edgelabel_color
          values$group_storage$sem[[group_id]]$last_apply_modify_params_edgelabel_fill <- apply_modify_params_edgelabel_fill
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_fill <- modify_params_edgelabel_fill
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_size <- modify_params_edgelabel_size
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_alpha <- modify_params_edgelabel_alpha
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_angle <- modify_params_edgelabel_angle
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_font <- modify_params_edgelabel_font
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_fontface <- modify_params_edgelabel_fontface
        }

        values$group_storage$sem[[group_id]]$last_modify_params_nodelabel <- modify_params_nodelabel
        if (modify_params_nodelabel == TRUE && !is.null(param_nodelabel_select) && length(param_nodelabel_select) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_select <- param_nodelabel_select
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_color <- modify_params_nodelabel_color
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_size <- modify_params_nodelabel_size
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_alpha <- modify_params_nodelabel_alpha
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_angle <- modify_params_nodelabel_angle
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_font <- modify_params_nodelabel_font
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_fontface <- modify_params_nodelabel_fontface
        }


        values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_text <- modify_params_nodelabel_text
        if (modify_params_nodelabel_text == TRUE && !is.null(params_nodelabel_text) && length(params_nodelabel_text) > 0) {
          values$group_storage$sem[[group_id]]$last_params_nodelabel_text <- params_nodelabel_text
          values$group_storage$sem[[group_id]]$last_params_nodelabel_nodelabel <- params_nodelabel_nodelabel
          values$group_storage$sem[[group_id]]$last_params_nodelabel_text_math_expression <- params_nodelabel_text_math_expression
        }

        values$group_storage$sem[[group_id]]$last_modify_params_node_xy <- modify_params_node_xy
        if (modify_params_node_xy == TRUE && !is.null(modify_params_node_select_xy) && length(modify_params_node_select_xy) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_node_shift_x <- modify_params_node_shift_x
          values$group_storage$sem[[group_id]]$last_modify_params_node_shift_y <- modify_params_node_shift_y
          values$group_storage$sem[[group_id]]$last_modify_which_node_shift_xy <- modify_params_node_select_xy
        }

        values$group_storage$sem[[group_id]]$last_modify_params_latent_node_xy <- modify_params_latent_node_xy
        if (modify_params_latent_node_xy == TRUE && !is.null(modify_params_latent_node_select_xy) && length(modify_params_latent_node_select_xy) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_latent_node_shift_x <- modify_params_latent_node_shift_x
          values$group_storage$sem[[group_id]]$last_modify_params_latent_node_shift_y <- modify_params_latent_node_shift_y
          values$group_storage$sem[[group_id]]$last_modify_which_latent_node_shift_xy <- modify_params_latent_node_select_xy
        }

        values$group_storage$sem[[group_id]]$last_modify_params_latent_node_angle <- modify_params_latent_node_angle
        if (modify_params_latent_node_angle == TRUE && !is.null(modify_params_latent_node_select_angle) && length(modify_params_latent_node_select_angle) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_latent_node_angle_value <- modify_params_latent_node_angle_value
          values$group_storage$sem[[group_id]]$last_modify_which_latent_node_angle <- modify_params_latent_node_select_angle
        }

        values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_xy <- modify_params_nodelabel_xy
        if (modify_params_nodelabel_xy == TRUE && !is.null(modify_params_nodelabel_xy_select) && length(modify_params_nodelabel_xy_select) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_shift_x <- modify_params_nodelabel_shift_x
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_shift_y <- modify_params_nodelabel_shift_y
          values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_xy_select <- modify_params_nodelabel_xy_select
        }

        values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_xy <- modify_params_edgelabel_xy
        if (modify_params_edgelabel_xy == TRUE && !is.null(modify_params_edgelabel_xy_select) && length(modify_params_edgelabel_xy_select) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_shift_x <- modify_params_edgelabel_shift_x
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_shift_y <- modify_params_edgelabel_shift_y
          values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_xy_select <- modify_params_edgelabel_xy_select
        }

        values$group_storage$sem[[group_id]]$last_modify_params_edge_xy <- modify_params_edge_xy
        if (modify_params_edge_xy == TRUE && !is.null(modify_params_edge_xy_select) && length(modify_params_edge_xy_select) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_edge_x_start <- modify_params_edge_x_start
          values$group_storage$sem[[group_id]]$last_modify_params_edge_x_end <- modify_params_edge_x_end
          values$group_storage$sem[[group_id]]$last_modify_params_edge_y_start <- modify_params_edge_y_start
          values$group_storage$sem[[group_id]]$last_modify_params_edge_y_end <- modify_params_edge_y_end
          values$group_storage$sem[[group_id]]$last_modify_params_edge_xy_select <- modify_params_edge_xy_select
        }


        values$group_storage$sem[[group_id]]$last_modify_params_cov_edge <- input$modify_params_cov_edge
        if (modify_params_cov_edge == TRUE && !is.null(param_cov_edge_select) && length(param_cov_edge_select) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_which_cov_edge <- param_cov_edge_select
          values$group_storage$sem[[group_id]]$last_modify_params_cov_edge_curvature_magnitude <- param_cov_edge_curvature_magnitude
          values$group_storage$sem[[group_id]]$last_modify_params_cov_edge_rotate_curvature <- param_cov_edge_rotate_curvature
          values$group_storage$sem[[group_id]]$last_modify_params_cov_edge_curvature_asymmetry <- param_cov_edge_curvature_asymmetry
          values$group_storage$sem[[group_id]]$last_modify_params_cov_edge_x_shift <- param_cov_edge_x_shift
          values$group_storage$sem[[group_id]]$last_modify_params_cov_edge_y_shift <- param_cov_edge_y_shift
        }

        values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_text <- modify_params_edgelabel_text
        if (modify_params_edgelabel_text == TRUE && !is.null(params_edgelabel_text) && length(params_edgelabel_text) > 0) {
          values$group_storage$sem[[group_id]]$last_params_edgelabel_text <- params_edgelabel_text
          values$group_storage$sem[[group_id]]$last_params_edgelabel_edgelabel <- params_edgelabel_edgelabel
          values$group_storage$sem[[group_id]]$last_params_edgelabel_text_math_expression <- params_edgelabel_text_math_expression
        }

        values$group_storage$sem[[group_id]]$last_sem_paths <- sem_paths
        values$group_storage$sem[[group_id]]$last_highlight_sig_path <- highlight_sig_path

        # Loop styling parameters
        values$group_storage$sem[[group_id]]$last_modify_params_loop <- modify_params_loop
        if (modify_params_loop == TRUE && !is.null(param_loop_select) && length(param_loop_select) > 0) {
          values$group_storage$sem[[group_id]]$last_param_loop_select <- param_loop_select
          values$group_storage$sem[[group_id]]$last_modify_params_loop_color <- modify_params_loop_color
          values$group_storage$sem[[group_id]]$last_modify_params_loop_alpha <- modify_params_loop_alpha
          values$group_storage$sem[[group_id]]$last_modify_params_loop_radius <- modify_params_loop_radius
          values$group_storage$sem[[group_id]]$last_modify_params_loop_width <- modify_params_loop_width
          values$group_storage$sem[[group_id]]$last_modify_params_loop_type <- modify_params_loop_type
          values$group_storage$sem[[group_id]]$last_modify_params_loop_arrow_size <- modify_params_loop_arrow_size
          values$group_storage$sem[[group_id]]$last_modify_params_loop_gap_size <- modify_params_loop_gap_size
          values$group_storage$sem[[group_id]]$last_modify_params_loop_two_way <- modify_params_loop_two_way
        }

        # Loop position shift parameters
        values$group_storage$sem[[group_id]]$last_modify_params_loop_xy <- modify_params_loop_xy
        if (modify_params_loop_xy == TRUE && !is.null(modify_params_loop_select_xy) && length(modify_params_loop_select_xy) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_loop_shift_x <- modify_params_loop_shift_x
          values$group_storage$sem[[group_id]]$last_modify_params_loop_shift_y <- modify_params_loop_shift_y
          values$group_storage$sem[[group_id]]$last_modify_which_loop_shift_xy <- modify_params_loop_select_xy
        }

        # Loop location parameters
        values$group_storage$sem[[group_id]]$last_modify_params_loop_location <- modify_params_loop_location
        if (modify_params_loop_location == TRUE && !is.null(modify_params_loop_select_location) && length(modify_params_loop_select_location) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_loop_location_value <- modify_params_loop_location_value
          values$group_storage$sem[[group_id]]$last_modify_which_loop_location <- modify_params_loop_select_location
        }

        # Loop label styling parameters
        values$group_storage$sem[[group_id]]$last_modify_params_looplabel <- modify_params_looplabel
        if (modify_params_looplabel == TRUE && !is.null(param_looplabel_select) && length(param_looplabel_select) > 0) {
          values$group_storage$sem[[group_id]]$last_param_looplabel_select <- param_looplabel_select
          values$group_storage$sem[[group_id]]$last_modify_params_looplabel_color <- modify_params_looplabel_color
          values$group_storage$sem[[group_id]]$last_modify_params_looplabel_size <- modify_params_looplabel_size
          values$group_storage$sem[[group_id]]$last_modify_params_looplabel_alpha <- modify_params_looplabel_alpha
          values$group_storage$sem[[group_id]]$last_modify_params_looplabel_angle <- modify_params_looplabel_angle
          values$group_storage$sem[[group_id]]$last_modify_params_looplabel_font <- modify_params_looplabel_font
          values$group_storage$sem[[group_id]]$last_modify_params_looplabel_fontface <- modify_params_looplabel_fontface
          values$group_storage$sem[[group_id]]$last_apply_modify_params_looplabel_fill <- apply_modify_params_looplabel_fill
          values$group_storage$sem[[group_id]]$last_modify_params_looplabel_fill <- modify_params_looplabel_fill
        }

        # Loop label position shift parameters
        values$group_storage$sem[[group_id]]$last_modify_params_looplabel_xy <- modify_params_looplabel_xy
        if (modify_params_looplabel_xy == TRUE && !is.null(modify_params_looplabel_xy_select) && length(modify_params_looplabel_xy_select) > 0) {
          values$group_storage$sem[[group_id]]$last_modify_params_looplabel_shift_x <- modify_params_looplabel_shift_x
          values$group_storage$sem[[group_id]]$last_modify_params_looplabel_shift_y <- modify_params_looplabel_shift_y
          values$group_storage$sem[[group_id]]$last_modify_which_looplabel_shift_xy <- modify_params_looplabel_xy_select
        }

        values$group_storage$sem[[group_id]]$last_modify_params_looplabel_text <- modify_params_looplabel_text
        if (modify_params_looplabel_text == TRUE && !is.null(params_looplabel_text) && length(params_looplabel_text) > 0) {
          values$group_storage$sem[[group_id]]$last_params_looplabel_text <- params_looplabel_text
          values$group_storage$sem[[group_id]]$last_params_looplabel_looplabel <- params_looplabel_looplabel
          values$group_storage$sem[[group_id]]$last_params_looplabel_text_math_expression <- params_looplabel_text_math_expression
        }


        # Nodes shapes and sizes
        values$group_storage$sem[[group_id]]$last_latent_shape <- latent_shape
        values$group_storage$sem[[group_id]]$last_observed_shape <- observed_shape
        values$group_storage$sem[[group_id]]$last_int_shape <- int_shape
        values$group_storage$sem[[group_id]]$last_point_size_latent <- latent_size_input
        values$group_storage$sem[[group_id]]$last_point_size_observed <- observed_size_input
        values$group_storage$sem[[group_id]]$last_point_size_int <- int_size_input
        values$group_storage$sem[[group_id]]$last_width_height_ratio_latent = width_height_ratio_latent
        values$group_storage$sem[[group_id]]$last_width_height_ratio_observed = width_height_ratio_observed
        values$group_storage$sem[[group_id]]$last_width_height_ratio_int = width_height_ratio_int

        # Line properties
        values$group_storage$sem[[group_id]]$last_line_width <- line_width
        values$group_storage$sem[[group_id]]$last_line_alpha <- line_alpha
        values$group_storage$sem[[group_id]]$last_line_endpoint_spacing <- line_endpoint_spacing
        values$group_storage$sem[[group_id]]$last_edge_color <- edge_color

        # Text properties - Latent
        values$group_storage$sem[[group_id]]$last_text_size_latent <- text_size_latent
        values$group_storage$sem[[group_id]]$last_text_font_latent <- text_font_latent
        values$group_storage$sem[[group_id]]$last_text_color_latent <- text_color_latent
        values$group_storage$sem[[group_id]]$last_text_alpha_latent <- text_alpha_latent
        values$group_storage$sem[[group_id]]$last_text_fontface_latent <- text_fontface_latent

        # Text properties - Others (Observed/Intercept)
        values$group_storage$sem[[group_id]]$last_text_size_others <- text_size_others
        values$group_storage$sem[[group_id]]$last_text_font_others <- text_font_others
        values$group_storage$sem[[group_id]]$last_text_color_others <- text_color_others
        values$group_storage$sem[[group_id]]$last_text_alpha_others <- text_alpha_others
        values$group_storage$sem[[group_id]]$last_text_fontface_others <- text_fontface_others

        # Text properties - Edges
        values$group_storage$sem[[group_id]]$last_text_size_edges <- text_size_edges
        values$group_storage$sem[[group_id]]$last_text_font_edges <- text_font_edges
        values$group_storage$sem[[group_id]]$last_text_color_edges <- text_color_edges
        values$group_storage$sem[[group_id]]$last_text_color_fill <- text_color_fill
        values$group_storage$sem[[group_id]]$last_apply_text_color_fill <- apply_fill_edges_color
        values$group_storage$sem[[group_id]]$last_text_alpha_edges <- text_alpha_edges
        values$group_storage$sem[[group_id]]$last_text_fontface_edges <- text_fontface_edges

        # Point colors
        values$group_storage$sem[[group_id]]$last_point_color_latent <- latent_color_input
        values$group_storage$sem[[group_id]]$last_point_color_observed <- observed_color_input
        values$group_storage$sem[[group_id]]$last_point_color_int <- int_color_input

        # Node border properties
        values$group_storage$sem[[group_id]]$last_node_border_color <- node_border_color
        values$group_storage$sem[[group_id]]$last_node_border_width <- node_border_width

        # General arrow properties
        values$group_storage$sem[[group_id]]$last_arrow_type <- lavaan_arrow_type
        values$group_storage$sem[[group_id]]$last_arrow_size <- lavaan_arrow_size

        values$group_storage$sem[[group_id]]$last_modify_params_edgelabel_xy <- modify_params_edgelabel_xy
        values$group_storage$sem[[group_id]]$last_modify_params_nodelabel_xy <- modify_params_nodelabel_xy
        values$group_storage$sem[[group_id]]$last_modify_params_edge_xy <- modify_params_edge_xy

        values$group_storage$sem[[group_id]]$last_lavaan_curved_x_shift <- lavaan_curved_x_shift
        values$group_storage$sem[[group_id]]$last_lavaan_curved_y_shift <- lavaan_curved_y_shift
        values$group_storage$sem[[group_id]]$last_remove_edgelabels <- remove_edgelabels
        values$group_storage$sem[[group_id]]$last_lavaan_rotate_curvature <- lavaan_rotate_curvature
        values$group_storage$sem[[group_id]]$last_lavaan_curvature_magnitude <- lavaan_curvature_magnitude
        values$group_storage$sem[[group_id]]$last_lavaan_curvature_asymmetry <- lavaan_curvature_asymmetry
        values$group_storage$sem[[group_id]]$last_relative_x_position <- relative_x_position
        values$group_storage$sem[[group_id]]$last_relative_y_position <- relative_y_position
        values$group_storage$sem[[group_id]]$last_center_x_position <- center_x_position
        values$group_storage$sem[[group_id]]$last_center_y_position <- center_y_position
        values$group_storage$sem[[group_id]]$last_flip_sem_layout <- flip_sem_layout
        values$group_storage$sem[[group_id]]$last_flip_sem_layout_direction <- flip_sem_layout_direction
        values$group_storage$sem[[group_id]]$last_rotate_sem_layout <- rotate_sem_layout
        values$group_storage$sem[[group_id]]$last_rotate_sem_layout_angle <- rotate_sem_layout_angle

        values$group_storage$sem[[group_id]]$last_sig_label_fontface <- sig_label_fontface
        values$group_storage$sem[[group_id]]$last_non_sig_label_fontface <- non_sig_label_fontface
        values$group_storage$sem[[group_id]]$last_sig_path_color <- sig_path_color
        values$group_storage$sem[[group_id]]$last_non_sig_path_color <- non_sig_path_color

        # loop
        values$group_storage$sem[[group_id]]$last_residuals <- residuals
        values$group_storage$sem[[group_id]]$last_lavaan_radius <- lavaan_radius
        values$group_storage$sem[[group_id]]$last_lavaan_width_loop <- lavaan_width_loop
        values$group_storage$sem[[group_id]]$last_lavaan_line_color_loop <- lavaan_line_color_loop
        values$group_storage$sem[[group_id]]$last_lavaan_line_alpha_loop <- lavaan_line_alpha_loop
        values$group_storage$sem[[group_id]]$last_lavaan_arrow_type_loop <- lavaan_arrow_type_loop
        values$group_storage$sem[[group_id]]$last_lavaan_arrow_size_loop <- lavaan_arrow_size_loop
        values$group_storage$sem[[group_id]]$last_lavaan_gap_size_loop <- lavaan_gap_size_loop
        values$group_storage$sem[[group_id]]$last_lavaan_height_loop <- lavaan_height_loop
        values$group_storage$sem[[group_id]]$last_lavaan_two_way_arrow_loop <- lavaan_two_way_arrow_loop
        values$group_storage$sem[[group_id]]$last_lavaan_loop_offset <- lavaan_loop_offset
        values$group_storage$sem[[group_id]]$last_residuals_orientation_type <- residuals_orientation_type
        values$group_storage$sem[[group_id]]$last_loop_names_remove_hi <- loop_names_remove

        lavaan_points <- which(values$points$lavaan == TRUE & values$points$group == group_id)

        if (length(lavaan_points) > 0) {
          values$points[lavaan_points, ] <- graph_data$points
        }

        lavaan_lines <- which(values$lines$lavaan == TRUE & values$lines$group == group_id)
        if (length(lavaan_lines) > 0) {
          values$lines[lavaan_lines, ] <- graph_data$lines
        }

        lavaan_loops <- which(values$loops$lavaan == TRUE & values$loops$group == group_id)

        if (residuals_exist_not_before) {
          lavaan_loops_group <- which(graph_data$loops$lavaan == TRUE & graph_data$loops$group == group_id)
          if (length(lavaan_loops_group) > 0) {
            values$loops <- rbind(values$loops, graph_data$loops[lavaan_loops_group,])
          }
        } else if (residuals_exist_no_longer) {
          if (length(lavaan_loops) > 0) {
            values$loops <- values$loops[-lavaan_loops,]
          }
        } else if (residuals_remove_changed) {
          if (residuals) {
            if (length(lavaan_loops) > 0) {
              values$loops <- values$loops[-lavaan_loops,]
            }
            lavaan_loops_group <- which(graph_data$loops$lavaan == TRUE & graph_data$loops$group == group_id)
            if (length(lavaan_loops_group) > 0) {
              values$loops <- rbind(values$loops, graph_data$loops[lavaan_loops_group,])
            }
          } else {
            if (length(lavaan_loops) > 0) {
              values$loops <- values$loops[-lavaan_loops,]
            }
          }
        } else if (residuals && length(lavaan_loops) > 0) {
          values$loops[lavaan_loops, ] <- graph_data$loops
        } else {
          # No changes needed
        }

        lavaan_annotations0 <- which(values$annotations$lavaan == TRUE & values$annotations$group == group_id)

        annotations_need_update <- FALSE
        annotations_need_removal <- FALSE

        # Condition 1: Residuals were just enabled (need to add annotations)
        if (residuals_exist_not_before) {
          annotations_need_update <- TRUE
        }

        # Condition 2: Residuals were just disabled (need to remove annotations)
        else if (residuals_exist_no_longer && !residuals) {
          annotations_need_removal <- TRUE
        }

        # Condition 3: Remove list changed but residuals still enabled (need to update annotations)
        else if (residuals_remove_changed && residuals) {
          annotations_need_update <- TRUE
          # First remove existing annotations if any exist
          if (length(lavaan_annotations0) > 0) {
            lavaan_loops_annotations0 <- which(values$annotations$loop_label == TRUE & values$annotations$group == group_id)
            values$annotations <- values$annotations[-lavaan_loops_annotations0,]
          }
        }

        # Condition 4: Residuals disabled but were previously enabled (need to remove)
        else if (!residuals && group_storage$last_residuals) {
          annotations_need_removal <- TRUE
        }

        if (length(lavaan_annotations0) > 0) {
          if (annotations_need_update) {
            # Add new annotations for residual loops
            lavaan_loops_annotations <- which(graph_data$annotations$loop_label == TRUE &
                                                graph_data$annotations$group == group_id)
            if (length(lavaan_loops_annotations) > 0) {
              values$annotations <- rbind(values$annotations,
                                          graph_data$annotations[lavaan_loops_annotations,])
            }
          } else if (annotations_need_removal) {
            # Remove existing annotations
            lavaan_loops_annotations <- which(values$annotations$loop_label == TRUE &
                                                values$annotations$group == group_id)
            if (length(lavaan_loops_annotations) > 0) {
              values$annotations <- values$annotations[-lavaan_loops_annotations,]
            }
          }

          lavaan_annotations <- which(values$annotations$lavaan == TRUE & values$annotations$group == group_id)

          # Store original order
          values$annotations$temp_order <- 1:nrow(values$annotations)

          # Get non-lavaan annotations (keep original order)
          annotations_etc <- values$annotations[-lavaan_annotations, ]

          if (nrow(graph_data$annotations) > 0) {
            if (nrow(graph_data$annotations) <= length(lavaan_annotations)) {
              new_positions <- lavaan_annotations[1:nrow(graph_data$annotations)]
            } else {
              new_positions <- c(
                lavaan_annotations,
                (max(values$annotations$temp_order) + 1):(max(values$annotations$temp_order) + nrow(graph_data$annotations) - length(lavaan_annotations))
              )
            }

            graph_data$annotations$temp_order <- new_positions
            values$annotations <- rbind(annotations_etc, graph_data$annotations)
            values$annotations <- values$annotations[order(values$annotations$temp_order), ]

          } else {
            values$annotations <- annotations_etc
          }

          # Clean up temporary column
          values$annotations$temp_order <- NULL

          no_changes <- !any(layout_changed, layout_flip_changed, layout_angle_changed, scaling_changed, positional_shift, positional_shift, curvature_flip,
                             curvature_changed, asymmetry_changed, y_shift, x_shift, stats_shift, model_current_changed, intercepts_changed,
                             param_node_changed, param_edge_changed, param_edgelabel_changed, param_node_xy_changed, param_edge_xy_changed, param_edgelabel_xy_changed,
                             param_edgelabel_text_changed, param_cov_edge_changed, param_nodelabel_changed, param_nodelabel_xy_changed, param_nodelabel_text_changed,
                             param_latent_node_xy_changed, param_latent_node_angle_changed, residuals_changed, residuals_remove_changed, param_looplabel_text_changed)

          if (no_changes) {
            values$annotations[lavaan_annotations, c('x', 'y')] <- original_xy
          }
        }

        lavaan_change_click$n_count <- lavaan_change_click$n_count + 1

        output$plot <- renderPlot({
          recreate_plot()
        })

        showNotification(
          HTML(paste("Successfully updated", "<b>SEM</b>", "visualization in group", "<b>", group_id, "</b>")),
          type = "message"
        )
      }
    }, error = function(e) {
      showNotification(
        paste("Error applying SEM changes:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observeEvent(input$apply_gradient, {
    req(input$group_select)
    tryCatch({
      save_state()
      group_id <- as.character(input$group_select)
      unlocked_points <- which(!values$points$locked & values$points$group == group_id)

      n_unlocked_points <- nrow(values$points[unlocked_points,])
      split_index <- round(input$gradient_position_points * n_unlocked_points)

      grad_start_color <- input$grad_start_color
      grad_end_color <- input$grad_end_color

      if (n_unlocked_points > 1) {

        color_interpolator <- colorRampPalette(c(grad_start_color, grad_end_color))
        intermediate_color <- color_interpolator(3)[2]

        gradient_colors_start <- colorRampPalette(c(grad_start_color, intermediate_color))(split_index)
        gradient_colors_end <- colorRampPalette(c(intermediate_color, grad_end_color))(n_unlocked_points - split_index)

        gradient_colors_layout <- c(gradient_colors_start,gradient_colors_end)

        values$points$color[unlocked_points] <- gradient_colors_layout


        output$plot <- renderPlot({
          recreate_plot()
        })

        showNotification(
          HTML(paste("Gradient applied successfully to unlocked", "<b>points</b>", "in group", "<b>", group_id, "</b>")),
          type = "message"
        )
      } else {

        showNotification(
          HTML(paste("Insufficient unlocked", "<b>points</b>", "in group", "<b>", group_id, "</b>", "to apply gradient")),
          type = "warning"
        )
      }
    }, error = function(e) {
      showNotification(
        paste("Error applying gradient to points:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$apply_line_gradient, {
    req(input$group_select)
    tryCatch({
      save_state()
      group_id <- as.character(input$group_select)
      unlocked_lines <- which(!values$lines$locked & values$lines$group == group_id)

      n_unlocked_lines <- nrow(values$lines[unlocked_lines,])
      split_index <- round(input$gradient_position_auto * n_unlocked_lines)

      if (n_unlocked_lines > 1) {
        grad_start_color <- input$line_color_auto
        grad_end_color <- input$end_color_auto

        color_interpolator <- colorRampPalette(c(grad_start_color, grad_end_color))
        intermediate_color <- color_interpolator(3)[2]

        gradient_colors_start <- colorRampPalette(c(grad_start_color, intermediate_color))(split_index)
        gradient_colors_end <- colorRampPalette(c(intermediate_color, grad_end_color))(n_unlocked_lines - split_index)

        gradient_colors_layout <- c(gradient_colors_start,gradient_colors_end)

        # Apply gradient colors to unlocked lines
        values$lines$color[unlocked_lines] <- gradient_colors_layout


        output$plot <- renderPlot({
          recreate_plot()
        })

        showNotification(
          HTML(paste("Gradient applied successfully to unlocked", "<b>lines</b>", "in group", "<b>", group_id, "</b>")),
          type = "message"
        )
      } else {
        showNotification(
          HTML(paste("Insufficient unlocked", "<b>lines</b>", "in group", "<b>", group_id, "</b>", "to apply gradient")),
          type = "warning"
        )
      }
    }, error = function(e) {
      showNotification(
        paste("Error applying gradient to lines:", e$message),
        type = "error",
        duration = 5
      )
    })
  })



  observeEvent(input$apply_text_gradient, {
    req(input$group_select)
    tryCatch({
      save_state()
      group_id <- as.character(input$group_select)
      unlocked_texts <- which(!values$annotations$locked & values$annotations$group == group_id)

      n_unlocked_texts <- nrow(values$annotations[unlocked_texts,])
      split_index <- round(input$gradient_position_texts * n_unlocked_texts)

      grad_start_color <- input$grad_start_color_texts
      grad_end_color <- input$grad_end_color_texts

      if (n_unlocked_texts > 1) {

        color_interpolator <- colorRampPalette(c(grad_start_color, grad_end_color))
        intermediate_color <- color_interpolator(3)[2]

        gradient_colors_start <- colorRampPalette(c(grad_start_color, intermediate_color))(split_index)
        gradient_colors_end <- colorRampPalette(c(intermediate_color, grad_end_color))(n_unlocked_texts - split_index)

        gradient_colors_layout <- c(gradient_colors_start,gradient_colors_end)

        values$annotations$color[unlocked_texts] <- gradient_colors_layout

        output$plot <- renderPlot({
          recreate_plot()
        })

        showNotification(
          HTML(paste("Gradient applied successfully to unlocked", "<b>annotations</b>", "in group", "<b>", group_id, "</b>")),
          type = "message"
        )
      } else {
        showNotification(
          HTML(paste("Insufficient unlocked", "<b>annotations</b>", "in group", "<b>", group_id, "</b>", "to apply gradient")),
          type = "warning"
        )
      }
    }, error = function(e) {
      showNotification(
        paste("Error applying gradient to annotations:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observeEvent(input$apply_loop_gradient, {
    req(input$group_select)
    tryCatch({
      group_id <- as.character(input$group_select)
      unlocked_loops <- which(!values$loops$locked & values$loops$group == group_id)

      n_unlocked_loops <- nrow(values$loops[unlocked_loops,])
      split_index <- round(input$gradient_position_loops * n_unlocked_loops)

      grad_start_color <- input$grad_start_color_loops
      grad_end_color <- input$grad_end_color_loops

      if (n_unlocked_loops > 1) {

        color_interpolator <- colorRampPalette(c(grad_start_color, grad_end_color))
        intermediate_color <- color_interpolator(3)[2]

        gradient_colors_start <- colorRampPalette(c(grad_start_color, intermediate_color))(split_index)
        gradient_colors_end <- colorRampPalette(c(intermediate_color, grad_end_color))(n_unlocked_loops - split_index)

        gradient_colors_layout <- c(gradient_colors_start,gradient_colors_end)

        values$loops$color[unlocked_loops] <- gradient_colors_layout

        output$plot <- renderPlot({
          recreate_plot()
        })

        showNotification(
          HTML(paste("Gradient applied successfully to unlocked", "<b>loops</b>", "in group", "<b>", group_id, "</b>")),
          type = "message"
        )
      } else {
        showNotification(
          HTML(paste("Insufficient unlocked", "<b>loops</b>", "in group", "<b>", group_id, "</b>", "to apply gradient")),
          type = "warning"
        )
      }
    }, error = function(e) {
      showNotification(
        paste("Error applying gradient to self-loop arrows:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  sem_file_data <- reactiveVal(NULL)

  observeEvent(input$edge_label_file, {
    sem_file_data(input$edge_label_file)
  })

  observeEvent(input$delete_sem_file, {
    sem_file_data(NULL)
    shinyjs::reset("edge_label_file")
    updateSelectInput(session, "sample_data_choice", selected = "")
  })


  observeEvent(input$sample_data_choice, {
    req(input$sample_data_choice)

    sample_file <- switch(input$sample_data_choice,
                          "holzinger" = "HolzingerSwineford1939.csv",
                          "economic" = "PoliticalDemocracy.csv",
                          "growth" = "DemoGrowth.csv"
    )

    # Set lavaan syntax based on the dataset
    lavaan_syntax <- switch(input$sample_data_choice,
                            "holzinger" = "visual  =~ x1 + x2 + x3
textual =~ x4 + x5 + x6
speed   =~ x7 + x8 + x9",
                            "economic" = "# measurement model
    ind60 =~ x1 + x2 + x3
    dem60 =~ y1 + y2 + y3 + y4
    dem65 =~ y5 + y6 + y7 + y8
  # regressions
    dem60 ~ ind60
    dem65 ~ ind60 + dem60
  # residual correlations
    y1 ~~ y5
    y2 ~~ y4 + y6
    y3 ~~ y7
    y4 ~~ y8
    y6 ~~ y8",
                            "growth" = "i =~ 1*t1 + 1*t2 + 1*t3 + 1*t4
s =~ 0*t1 + 1*t2 + 2*t3 + 3*t4"
    )

    sem_model_type <- switch(input$sample_data_choice,
                             "holzinger" = "cfa",
                             "economic" = "sem",
                             "growth" = "growth")

    # Construct path to sample data
    sample_path <- system.file("sample_data", sample_file, package = "ggsem")

    # Update the lavaan syntax text area
    updateTextAreaInput(
      session = session,
      inputId = "lavaan_syntax",
      value = lavaan_syntax
    )

    updateSelectInput(session,
                      "sem_model_type", choices = c(
                        "SEM" = "sem",
                        "CFA" = "cfa",
                        "EFA" = "efa",
                        "Growth" = "growth"
                      ), selected = sem_model_type)

    if (file.exists(sample_path)) {
      fake_file_input <- list(
        name = sample_file,
        size = file.info(sample_path)$size,
        type = "text/csv",
        datapath = sample_path
      )

      sem_file_data(fake_file_input)

      showNotification(paste("Loaded", input$sample_data_choice, "sample data"),
                       type = "message")
    } else {
      showNotification("Sample data file not found. Maybe re-install the package?", type = "warning")
    }
  })

  observeEvent(input$edge_label_file, {
    updateSelectInput(session, "sample_data_choice", selected = "")
  })

  last_valid_network_data <- reactiveVal(NULL)

  network_file_data <- reactiveVal(NULL)

  observeEvent(input$network_file, {
    network_file_data(input$network_file)
  })

  observeEvent(input$delete_network_file, {
    network_file_data(NULL)
    shinyjs::reset("network_file")
  }
  )

  observeEvent(input$generate_network, {
    tryCatch({
      if (is.null(network_file_data()$datapath)) {
        showNotification("Network data file CSV required.", type = "error")
        return()
      }

      req(network_file_data()$datapath)

      network_df <- read.csv(network_file_data()$datapath)
      if ("source" %in% colnames(network_df) && "target" %in% colnames(network_df)) {
        # If it's an edge list
        edges <- network_df
      } else if (is.matrix(network_df) || is.data.frame(network_df)) {
        network_df <- read.csv(network_file_data()$datapath, row.names = 1)
        # If it's an adjacency matrix, convert to edge list
        if (is.null(colnames(network_df)) || is.null(rownames(network_df))) {
          stop("Adjacency matrix must have row and column names representing node identifiers.")
        }

        edges <- which(network_df != 0, arr.ind = TRUE) |>
          as.data.frame() |>
          dplyr::rename(source = row, target = col) |>
          dplyr::mutate(
            source = rownames(network_df)[source],
            target = colnames(network_df)[target],
            weight = as.vector(network_df[which(network_df != 0, arr.ind = TRUE)]) # Include weights if matrix is weighted
          )
        if (all(edges$weight == 1)) {
          network_state$weights <- NULL # annotate_edges = FALSE
        }

      } else {
        stop("Invalid input: The input must be either an edge list or adjacency matrix.")
      }

      unique_nodes <- unique(c(edges$source, edges$target))
      network_state$nodes <- data.frame(node = unique_nodes)

      edges <- edges |>
        mutate(
          edge_id = pmin(source, target),
          edge_pair = pmax(source, target)
        ) |>
        group_by(edge_id, edge_pair) |>
        summarise(
          source = dplyr::first(source),
          target = dplyr::first(target),
          weight = if ("weight" %in% colnames(edges)) {
            if (is.numeric(weight)) {
              # Handle numeric weights with NA control
              if (all(is.na(weight))) NA_real_
              else mean(weight, na.rm = TRUE)
            } else {
              # Handle character/string weights
              if (all(is.na(weight))) NA_character_
              else dplyr::first(stats::na.omit(weight)) # Take first non-NA value
            }
          }
          ,
          two_way = dplyr::n() > 1,
          .groups = "drop"
        )

      # network_state$edges <- edges
      save_state()
      group_id <- as.character(debounced_which_group())

      if (is.null(values$group_storage$modifications_network[[group_id]])) {
        if (is.null(values$group_storage$modifications_network)) {
          values$group_storage$modifications_network <- list()
        }
        values$group_storage$modifications_network[[group_id]] <- create_mod_defaults(type = "network")
      }

      if (is.null(values$group_storage$network[[group_id]])) {
        if (is.null(values$group_storage$network)) {
          values$group_storage$network <- list()
        }
        values$group_storage$network[[group_id]] <- create_group_storage(type = "network")
      }

      values$group_storage$network[[group_id]]$data <- network_df
      values$group_storage$network[[group_id]]$edges <- edges

      nodes <- data.frame(node = unique_nodes)

      values$group_storage$network[[group_id]]$nodes <- nodes

      layout_method <- input$layout_method


      network_prep <- generate_network_layout(network_object = NULL,
                                              edges = edges,
                                              nodes = nodes,
                                              layout_method = ifelse(layout_method == 'layout_ai', "fr", layout_method),
                                              directed = input$is_directed,
                                              dim_reduction_method = input$dim_reduction_method,
                                              random_seed = input$random_seed,
                                              flip_layout = input$flip_network_layout,
                                              flip_direction = input$flip_network_layout_direction,
                                              rotate_layout = input$rotate_network_layout,
                                              rotate_angle = input$rotate_network_layout_angle)

      if (layout_method == 'layout_ai') {
        if (input$ai_model %in% c("gemini", "openai", "mistral", "claude")) {
          api_key <- switch(
            input$ai_model,
            "gemini" = input$gemini_api_key,
            "openai" = input$openai_api_key,
            "mistral" = input$mistral_api_key,
            "claude" = input$claude_api_key
          )

          tryCatch({
            validate_api_key(api_key, input$ai_model)
          }, error = function(e) {
            showNotification(
              paste("API key validation failed for", input$ai_model, ":", e$message),
              type = "error"
            )
            return(NULL)
          })
        }

        ai_settings <- list(
          gemini = input$gemini_api_key,
          openai = input$openai_api_key,
          mistral = input$mistral_api_key,
          claude = input$claude_api_key,
          ollama_model = input$ollama_model
        )

        showModal(modalDialog(
          title = paste("Generating network layout with", input$ai_model, "..."),
          "Please wait while AI generates network layout",
          footer = NULL,
          easyClose = FALSE
        ))

        prompt <- create_network_layout_prompt(network_prep$layout, comments = input$additional_prompts_network_layout)
        ai_response <- tryCatch({
          call_selected_ai(prompt, input$ai_model, ai_settings)
        }, error = function(e) {
          removeModal()
          showNotification(
            paste("Error calling AI model:", e$message),
            type = "error"
          )
          return(NULL)
        })

        if (!is.null(ai_response)) {
          layout <- tryCatch({
            parse_ai_layout_response(ai_response)
          }, error = function(e) {
            showNotification(
              paste("Error parsing AI response:", e$message),
              type = "error"
            )
            return(layout) # keep old coords if parsing fails
          })

          removeModal()
          showNotification("Network layout generated successfully with GenAI!", type = "message")
        }
      } else {
        layout <- network_prep$layout
      }

      edge_label_fill <- if (input$apply_edge_label_fill) input$edge_label_fill else NA

      # Process network data
      network_graph <- generate_graph_from_network(
        graph = network_prep$graph,
        layout = layout,
        is_bipartite = network_prep$is_bipartite,
        edges = edges,
        nodes = nodes,
        directed = input$is_directed,
        layout_width = input$layout_x_net,
        layout_height = input$layout_y_net,
        x_center = input$x_center_net,
        y_center = input$y_center_net,
        node_shape = input$node_shape_net,
        node_size = input$node_size_net,
        node_alpha = input$node_alpha_net,
        node_fill_color = input$node_fill_color_net,
        node_border_color = input$node_border_color_net,
        node_border_width = input$node_border_width_net,
        node_width_height_ratio = input$node_width_height_ratio_net,
        line_width = input$line_width_net,
        line_color = input$line_color_net,
        line_alpha = input$line_alpha_net,
        min_edge_width = ifelse(input$scale_edge_width, input$min_edge_width, NULL),
        max_edge_width = ifelse(input$scale_edge_width, input$max_edge_width, NULL),
        scale_by_weight = input$scale_edge_width,
        line_endpoint_spacing = input$line_endpoint_spacing_net,
        arrow_type = input$arrow_type_net,
        arrow_size = input$arrow_size_net,
        node_label_font = input$node_label_font,
        node_label_size = input$node_label_size,
        node_label_color = input$node_label_color,
        node_label_alpha = input$node_label_alpha,
        node_label_fontface = input$node_label_fontface,
        edge_label_font = input$edge_label_font,
        edge_label_size = input$edge_label_size,
        edge_label_color = input$edge_label_color,
        edge_label_fill = edge_label_fill,
        edge_label_alpha = input$edge_label_alpha,
        edge_label_fontface = input$edge_label_fontface,
        zoom_factor = input$zoom,
        annotate_nodes = TRUE,
        annotate_edges = TRUE,
        remove_edgelabels = input$remove_edgelabels_net,
        use_clustering = input$use_clustering,
        clustering_method = input$clustering_method,
        cluster_palette = input$cluster_palette,
        bezier_network_edges = input$bezier_network_edges,
        network_edges_curvature_magnitude = input$network_edges_curvature_magnitude,
        network_edges_rotate_curvature = input$network_edges_rotate_curvature,
        network_edges_curvature_asymmetry = input$network_edges_curvature_asymmetry,
        which_group = debounced_which_group()
      )

      last_valid_network_data(network_file_data()$datapath)

      if (layout_method == 'layout_ai') values$group_storage$network[[group_id]]$layout <- layout # keep the template for AI layout, not used for others


      if (input$layout_method == "dim_reduction") {
        values$group_storage$network[[group_id]]$last_dim_reduction_method <- input$dim_reduction_method
      } else {
        values$group_storage$network[[group_id]]$last_dim_reduction_method <- NULL
      }

      node_fill_color_net_gradient <- input$node_fill_color_net_gradient
      gradient_position_points_net <- input$gradient_position_points_net
      grad_start_color_net <- input$grad_start_color_net
      grad_end_color_net <- input$grad_end_color_net


      if (node_fill_color_net_gradient) {

        n_unlocked_points <- nrow(network_graph$points)
        split_index <- round(gradient_position_points_net * n_unlocked_points)

        grad_start_color <- grad_start_color_net
        grad_end_color <- grad_end_color_net

        if (n_unlocked_points > 1) {

          color_interpolator <- colorRampPalette(c(grad_start_color, grad_end_color))
          intermediate_color <- color_interpolator(3)[2]

          gradient_colors_start <- colorRampPalette(c(grad_start_color, intermediate_color))(split_index)
          gradient_colors_end <- colorRampPalette(c(intermediate_color, grad_end_color))(n_unlocked_points - split_index)

          gradient_colors_layout <- c(gradient_colors_start,gradient_colors_end)

          network_graph$points$color <- gradient_colors_layout

        }
      }

      values$group_storage$network[[group_id]]$last_node_fill_color_net_gradient <- node_fill_color_net_gradient
      values$group_storage$network[[group_id]]$last_grad_start_color_net <- grad_start_color_net
      values$group_storage$network[[group_id]]$last_grad_end_color_net <- grad_end_color_net
      values$group_storage$network[[group_id]]$last_gradient_position_points_net <- gradient_position_points_net

      values$group_storage$network[[group_id]]$last_layout_x <- input$layout_x_net
      values$group_storage$network[[group_id]]$last_layout_y <- input$layout_y_net
      values$group_storage$network[[group_id]]$last_x_center <- input$x_center_net
      values$group_storage$network[[group_id]]$last_y_center <- input$y_center_net
      values$group_storage$network[[group_id]]$last_random_seed <- input$random_seed
      values$group_storage$network[[group_id]]$last_layout_method <- layout_method
      values$group_storage$network[[group_id]]$last_flip_network_layout <- input$flip_network_layout
      values$group_storage$network[[group_id]]$last_flip_network_layout_direction <- input$flip_network_layout_direction
      values$group_storage$network[[group_id]]$last_rotate_network_layout <- input$rotate_network_layout
      values$group_storage$network[[group_id]]$last_rotate_network_layout_angle <- input$rotate_network_layout_angle

      # Network structure
      values$group_storage$network[[group_id]]$bundleObject <- graph_from_data_frame(d = edges, vertices = nodes, directed = input$is_directed)
      values$group_storage$network[[group_id]]$last_is_directed <- input$is_directed
      values$group_storage$network[[group_id]]$last_use_clustering <- input$use_clustering
      values$group_storage$network[[group_id]]$last_clustering_method <- input$clustering_method
      values$group_storage$network[[group_id]]$last_cluster_palette <- input$cluster_palette

      # Node properties
      values$group_storage$network[[group_id]]$last_node_shape <- input$node_shape_net
      values$group_storage$network[[group_id]]$last_node_size <- input$node_size_net
      values$group_storage$network[[group_id]]$last_node_alpha <- input$node_alpha_net
      values$group_storage$network[[group_id]]$last_node_fill_color <- input$node_fill_color_net
      values$group_storage$network[[group_id]]$last_node_border_color <- input$node_border_color_net
      values$group_storage$network[[group_id]]$last_node_border_width <- input$node_border_width_net
      values$group_storage$network[[group_id]]$last_node_width_height_ratio <- input$node_width_height_ratio_net

      # Edge/Line properties
      values$group_storage$network[[group_id]]$last_line_width <- input$line_width_net
      values$group_storage$network[[group_id]]$last_line_color <- input$line_color_net
      values$group_storage$network[[group_id]]$last_line_alpha <- input$line_alpha_net
      values$group_storage$network[[group_id]]$last_min_edge_width <- if (input$scale_edge_width) input$min_edge_width else NULL
      values$group_storage$network[[group_id]]$last_max_edge_width <- if (input$scale_edge_width) input$max_edge_width else NULL
      values$group_storage$network[[group_id]]$last_scale_edge_width <- input$scale_edge_width
      values$group_storage$network[[group_id]]$last_line_endpoint_spacing <- input$line_endpoint_spacing_net
      values$group_storage$network[[group_id]]$last_bezier_network_edges <- input$bezier_network_edges


      # Arrow properties
      values$group_storage$network[[group_id]]$last_arrow_type <- input$arrow_type_net
      values$group_storage$network[[group_id]]$last_arrow_size <- input$arrow_size_net

      # Node label properties
      values$group_storage$network[[group_id]]$last_node_label_font <- input$node_label_font
      values$group_storage$network[[group_id]]$last_node_label_size <- input$node_label_size
      values$group_storage$network[[group_id]]$last_node_label_color <- input$node_label_color
      values$group_storage$network[[group_id]]$last_node_label_alpha <- input$node_label_alpha
      values$group_storage$network[[group_id]]$last_node_label_fontface <- input$node_label_fontface

      # Edge label properties
      values$group_storage$network[[group_id]]$last_edge_label_font <- input$edge_label_font
      values$group_storage$network[[group_id]]$last_edge_label_size <- input$edge_label_size
      values$group_storage$network[[group_id]]$last_edge_label_color <- input$edge_label_color
      values$group_storage$network[[group_id]]$last_apply_edge_label_fill <- input$apply_edge_label_fill
      values$group_storage$network[[group_id]]$last_edge_label_fill <- edge_label_fill
      values$group_storage$network[[group_id]]$last_edge_label_alpha <- input$edge_label_alpha
      values$group_storage$network[[group_id]]$last_edge_label_fontface <- input$edge_label_fontface
      values$group_storage$network[[group_id]]$last_remove_edgelabels_net <- input$remove_edgelabels_net

      values$group_storage$network[[group_id]]$last_annotate_edges <- input$annotate_edges
      values$group_storage$network[[group_id]]$last_network_edges_rotate_curvature <- input$network_edges_rotate_curvature
      values$group_storage$network[[group_id]]$last_network_edges_curvature_magnitude <- input$network_edges_curvature_magnitude
      values$group_storage$network[[group_id]]$last_network_edges_curvature_asymmetry <- input$network_edges_curvature_asymmetry

      # Update reactive values
      values$points <- rbind(values$points, network_graph$points)
      values$lines <- rbind(values$lines, network_graph$lines)
      values$annotations <- rbind(values$annotations, network_graph$annotations)

      # Trigger plot redraw
      output$plot <- renderPlot({
        recreate_plot()
      })

      update_group(1)

      # Notify user
      showNotification("Network diagram generated successfully.", type = "message")
    }, error = function(e) {
      showNotification(
        paste("Error generating network:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$apply_changes_network, {
    tryCatch({

      group_id <- as.character(input$group_select)

      if (is.null(values$group_storage$modifications_network[[group_id]])) {
        if (is.null(values$group_storage$modifications_network)) {
          values$group_storage$modifications_network <- list()
        }
        values$group_storage$modifications_network[[group_id]] <- create_mod_defaults(type = "network")
      }

      if (is.null(values$group_storage$network[[group_id]])) {
        if (is.null(values$group_storage$network)) {
          values$group_storage$network <- list()
        }
        values$group_storage$network[[group_id]] <- create_group_storage(type = "sem")
      }

      save_state()

      bundleObject <- values$group_storage$network[[group_id]]$bundleObject

      network_lines0 <- values$lines[values$lines$network & values$lines$group == group_id, ]
      network_points0 <- values$points[values$points$network & values$points$group == group_id, ]
      network_annotations0 <- values$annotations[values$annotations$network & values$annotations$group == group_id, ]


      if (nrow(network_lines0) == 0 && nrow(network_points0) == 0) {
        showNotification(
          HTML(paste("No network nodes or edges in group", "<b>", group_id, "</b>", "to apply changes.")),
          type = "warning"
        )
      } else {

        # Layout
        layout_method <- input$layout_method
        layout_x_net <- input$layout_x_net
        layout_y_net <- input$layout_y_net
        random_seed <- input$random_seed
        x_center_net <- input$x_center_net
        y_center_net <- input$y_center_net
        dim_reduction_method <- input$dim_reduction_method
        is_directed <- input$is_directed
        use_clustering = input$use_clustering
        clustering_method = input$clustering_method
        cluster_palette = input$cluster_palette

        if (!is.null(bundleObject) && inherits(bundleObject, "ggplot")) {
          if (network_change_click$n_count == 0) {
            layout_changed <- FALSE
            values$group_storage$network[[group_id]]$last_layout_method <- NULL
            values$group_storage$network[[group_id]]$last_dim_reduction_method <- NULL
            values$group_storage$network[[group_id]]$last_bezier_curvature <- 0.5
            values$group_storage$network[[group_id]]$last_bezier_asymmetry <- 0
            values$group_storage$network[[group_id]]$last_modify_params_edge_network <- FALSE
            values$group_storage$network[[group_id]]$last_modify_params_bezier_network_edges <- FALSE
            values$group_storage$network[[group_id]]$last_param_nodelabel_network_select <- FALSE
            values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network <- FALSE
            values$group_storage$network[[group_id]]$last_modify_params_node_network <- FALSE
            values$group_storage$network[[group_id]]$last_param_nodelabel_text_network_select <- FALSE
          } else {
            layout_changed <- !identical(layout_method, values$group_storage$network[[group_id]]$last_layout_method)
          }
        } else if (!is.null(bundleObject) && (inherits(bundleObject, "igraph") || inherits(bundleObject, "network") || inherits(bundleObject, "qgraph"))) {
          if (network_change_click$n_count == 0) { # first session
            values$group_storage$network[[group_id]]$last_layout_method = 'fr'
            values$group_storage$network[[group_id]]$last_dim_reduction_method <- NULL
            values$group_storage$network[[group_id]]$last_bezier_curvature <- 0.5
            values$group_storage$network[[group_id]]$last_bezier_asymmetry <- 0
            values$group_storage$network[[group_id]]$last_modify_params_edge_network <- FALSE
            values$group_storage$network[[group_id]]$last_modify_params_bezier_network_edges <- FALSE
            values$group_storage$network[[group_id]]$last_param_nodelabel_network_select <- FALSE
            values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network <- FALSE
            values$group_storage$network[[group_id]]$last_modify_params_node_network <- FALSE
            values$group_storage$network[[group_id]]$last_param_nodelabel_text_network_select <- FALSE
          }
          layout_changed <- !identical(layout_method, values$group_storage$network[[group_id]]$last_layout_method)
        } else {
          layout_changed <- if (values$group_storage$network[[group_id]]$last_layout_method == "dim_reduction") {
            !identical(layout_method, values$group_storage$network[[group_id]]$last_layout_method) ||
              !identical(dim_reduction_method, values$group_storage$network[[group_id]]$last_dim_reduction_method)
          } else {
            !identical(layout_method,  values$group_storage$network[[group_id]]$last_layout_method)
          }
        }

        if (is.null(values$group_storage$network[[group_id]])) {
          showNotification('Group storage not initialized. Please reselect group.', type = 'error')
          return()
        }

        group_storage <- values$group_storage$network[[group_id]]

        # Node properties
        node_shape_net <- input$node_shape_net
        node_size_net <- input$node_size_net
        node_alpha_net <- input$node_alpha_net
        node_fill_color_net <- input$node_fill_color_net
        node_border_color_net <- input$node_border_color_net
        node_border_width_net <- input$node_border_width_net
        node_width_height_ratio_net <- input$node_width_height_ratio_net

        # Edge/Line properties
        line_width_net <- input$line_width_net
        line_color_net <- input$line_color_net
        line_alpha_net <- input$line_alpha_net
        min_edge_width <- if (input$scale_edge_width) input$min_edge_width else NULL
        max_edge_width <- if (input$scale_edge_width) input$max_edge_width else NULL
        scale_edge_width <- input$scale_edge_width
        line_endpoint_spacing <- input$line_endpoint_spacing_net
        bezier_network_edges <- input$bezier_network_edges
        network_edges_curvature_magnitude <- input$network_edges_curvature_magnitude
        network_edges_curvature_asymmetry <- input$network_edges_curvature_asymmetry

        # Arrow properties
        arrow_type_net <- input$arrow_type_net
        arrow_size_net <- input$arrow_size_net

        # Node label properties
        node_label_font <- input$node_label_font
        node_label_size <- input$node_label_size
        node_label_color <- input$node_label_color
        node_label_alpha <- input$node_label_alpha
        node_label_fontface <- input$node_label_fontface

        # Edge label properties
        edge_label_font <- input$edge_label_font
        edge_label_size <- input$edge_label_size
        edge_label_color <- input$edge_label_color
        apply_edge_label_fill <- input$apply_edge_label_fill
        edge_label_fill <- if (apply_edge_label_fill) input$edge_label_fill else NA
        edge_label_alpha <- input$edge_label_alpha
        edge_label_fontface <- input$edge_label_fontface
        annotate_edges <- input$annotate_edges
        network_edges_rotate_curvature <- input$network_edges_rotate_curvature
        remove_edgelabels_net = input$remove_edgelabels_net

        if (is.null(remove_edgelabels_net) || is.na(remove_edgelabels_net)) remove_edgelabels_net <- FALSE


        modify_params_edge_network <- input$modify_params_edge_network
        modify_params_bezier_network_edges <- input$modify_params_bezier_network_edges
        modify_params_edgelabel_network <- input$modify_params_edgelabel_network
        modify_params_node_network <- input$modify_params_node_network
        param_nodelabel_text_network_select <- input$param_nodelabel_text_network_select

        # XY modification parameters
        modify_params_edge_xy_network <- input$modify_params_edge_xy_network
        modify_params_nodelabel_xy_network <- input$modify_params_nodelabel_xy_network
        modify_params_node_xy_network <- input$modify_params_node_xy_network
        modify_params_edgelabel_xy_network <- input$modify_params_edgelabel_xy_network


        # Parameter selection states
        param_edge_network_select <- input$param_edge_network_select
        param_bezier_edge_network_select <- input$param_bezier_edge_network_select
        param_edgelabel_network_select <- input$param_edgelabel_network_select
        param_node_network_select <- input$param_node_network_select
        param_nodelabel_network_select <- input$param_nodelabel_network_select

        param_edge_xy_network_select <- input$param_edge_xy_network_select
        param_node_xy_network_select <- input$param_node_xy_network_select
        param_edgelabel_xy_network_select <- input$param_edgelabel_xy_network_select
        param_nodelabel_xy_network_select <- input$param_nodelabel_xy_network_select

        # Edge modification parameters
        modify_params_edge_network_color <- input$modify_params_edge_network_color
        modify_params_edge_network_linewidth <- input$modify_params_edge_network_linewidth
        modify_params_edge_network_alpha <- input$modify_params_edge_network_alpha
        modify_params_edge_network_line_style <- input$modify_params_edge_network_line_style
        modify_params_edge_network_gradient_position <- input$modify_params_edge_network_gradient_position
        modify_params_edge_network_end_color <- input$modify_params_edge_network_end_color
        modify_params_edge_network_color_type <- input$modify_params_edge_network_color_type

        # Bezier edge modification parameters
        modify_params_network_edges_curvature_magnitude <- input$modify_params_network_edges_curvature_magnitude
        modify_params_network_edges_curvature_asymmetry <- input$modify_params_network_edges_curvature_asymmetry
        modify_params_network_edges_rotate_curvature <- input$modify_params_network_edges_rotate_curvature

        # Node label modification parameters
        modify_params_nodelabel_network <- input$modify_params_nodelabel_network
        modify_params_nodelabel_text_network <- input$modify_params_nodelabel_text_network
        modify_params_nodelabel_network_color <- input$modify_params_nodelabel_network_color
        modify_params_nodelabel_network_size <- input$modify_params_nodelabel_network_size
        modify_params_nodelabel_network_alpha <- input$modify_params_nodelabel_network_alpha
        modify_params_nodelabel_network_angle <- input$modify_params_nodelabel_network_angle
        modify_params_nodelabel_network_font <- input$modify_params_nodelabel_network_font
        modify_params_nodelabel_network_fontface <- input$modify_params_nodelabel_network_fontface

        # Edge label modification parameters
        modify_params_edgelabel_network_color <- input$modify_params_edgelabel_network_color
        apply_modify_params_edgelabel_network_fill <- input$apply_modify_params_edgelabel_network_fill
        modify_params_edgelabel_network_fill <- if (apply_modify_params_edgelabel_network_fill) input$modify_params_edgelabel_network_fill else NA
        modify_params_edgelabel_network_size <- input$modify_params_edgelabel_network_size
        modify_params_edgelabel_network_alpha <- input$modify_params_edgelabel_network_alpha
        modify_params_edgelabel_network_angle <- input$modify_params_edgelabel_network_angle
        modify_params_edgelabel_network_font <- input$modify_params_edgelabel_network_font
        modify_params_edgelabel_network_fontface <- input$modify_params_edgelabel_network_fontface

        # Node modification parameters
        modify_params_node_network_color <- input$modify_params_node_network_color
        modify_params_node_network_alpha <- input$modify_params_node_network_alpha
        modify_params_node_network_shape <- input$modify_params_node_network_shape
        modify_params_node_network_size <- input$modify_params_node_network_size
        modify_params_node_network_border_color <- input$modify_params_node_network_border_color
        modify_params_node_network_border_width <- input$modify_params_node_network_border_width
        modify_params_node_network_width_height_ratio <- input$modify_params_node_network_width_height_ratio

        # Node label text parameters
        params_nodelabel_network_nodelabel <- input$modify_params_nodelabel_network_nodelabel
        params_nodelabel_text_network_math_expression <- input$modify_params_nodelabel_text_network_math_expression

        # XY shifts
        modify_param_network_edge_x_shift <- safe_numeric_input(input$modify_param_network_edge_x_shift)
        modify_param_network_edge_y_shift <- safe_numeric_input(input$modify_param_network_edge_y_shift)
        modify_params_nodelabel_network_shift_x <- safe_numeric_input(input$modify_params_nodelabel_network_shift_x)
        modify_params_nodelabel_network_shift_y <- safe_numeric_input(input$modify_params_nodelabel_network_shift_y)
        network_node_shift_x <- safe_numeric_input(input$network_node_shift_x)
        network_node_shift_y <- safe_numeric_input(input$network_node_shift_y)
        modify_params_edgelabel_network_shift_x <- safe_numeric_input(input$modify_params_edgelabel_network_shift_x)
        modify_params_edgelabel_network_shift_y <- safe_numeric_input(input$modify_params_edgelabel_network_shift_y)
        modify_params_edge_network_start_shift_x <- safe_numeric_input(input$modify_params_edge_network_start_shift_x)
        modify_params_edge_network_start_shift_y <- safe_numeric_input(input$modify_params_edge_network_start_shift_y)
        modify_params_edge_network_end_shift_x <- safe_numeric_input(input$modify_params_edge_network_end_shift_x)
        modify_params_edge_network_end_shift_y <- safe_numeric_input(input$modify_params_edge_network_end_shift_y)


        is_bipartite <- if (inherits(bundleObject, "igraph")) {
          igraph::is_bipartite(bundleObject)
        } else if (inherits(bundleObject, "network")) {
          network::is.bipartite(bundleObject)
        } else {
          FALSE
        }

        if (is_bipartite) layout_changed <- FALSE

        flip_network_layout = input$flip_network_layout
        flip_network_layout_direction = input$flip_network_layout_direction
        rotate_network_layout = input$rotate_network_layout
        rotate_network_layout_angle = input$rotate_network_layout_angle

        random_seed_changed <- !identical(random_seed, group_storage$last_random_seed)
        scaling_changed <- !identical(layout_x_net, group_storage$last_layout_x) || !identical(layout_y_net, group_storage$last_layout_y)
        positional_shift <- !identical(x_center_net, group_storage$last_x_center) || !identical(y_center_net, group_storage$last_y_center)
        bezier_shift <- bezier_network_edges || !identical(network_edges_curvature_magnitude, group_storage$last_network_edges_curvature_magnitude)
        asymmetry_changed <- !identical(network_edges_curvature_asymmetry, group_storage$last_network_edges_curvature_asymmetry)

        layout_flip_changed <- !identical(flip_network_layout, group_storage$last_flip_network_layout) || !identical(flip_network_layout_direction, group_storage$last_flip_network_layout_direction)
        if (is.na(layout_flip_changed)) layout_flip_changed <- FALSE

        layout_angle_changed <- !identical(rotate_network_layout, group_storage$last_rotate_network_layout) || !identical(rotate_network_layout_angle, group_storage$last_rotate_network_layout_angle)
        if (is.na(layout_angle_changed)) layout_angle_changed <- FALSE

        params_edge_network_changed <- !identical(group_storage$last_modify_params_edge_network, modify_params_edge_network) ||
          !identical(group_storage$last_param_edge_network_select, param_edge_network_select) ||
          !identical(group_storage$last_modify_params_edge_network_color, modify_params_edge_network_color) ||
          !identical(group_storage$last_modify_params_edge_network_linewidth, modify_params_edge_network_linewidth) ||
          !identical(group_storage$last_modify_params_edge_network_alpha, modify_params_edge_network_alpha) ||
          !identical(group_storage$last_modify_params_edge_network_line_style, modify_params_edge_network_line_style) ||
          !identical(group_storage$last_modify_params_edge_network_gradient_position, modify_params_edge_network_gradient_position) ||
          !identical(group_storage$last_modify_params_edge_network_end_color, modify_params_edge_network_end_color) ||
          !identical(group_storage$last_modify_params_edge_network_color_type, modify_params_edge_network_color_type) ||
          !identical(group_storage$last_modify_params_edge_network_start_shift_x, modify_params_edge_network_start_shift_x) ||
          !identical(group_storage$last_modify_params_edge_network_start_shift_y, modify_params_edge_network_start_shift_y) ||
          !identical(group_storage$last_modify_params_edge_network_end_shift_x, modify_params_edge_network_end_shift_x) ||
          !identical(group_storage$last_modify_params_edge_network_end_shift_y, modify_params_edge_network_end_shift_y)

        if (is.na(params_edge_network_changed)) params_edge_network_changed <- FALSE

        params_bezier_edge_network_changed <- !identical(group_storage$last_modify_params_bezier_network_edges, modify_params_bezier_network_edges) ||
          !identical(group_storage$last_param_bezier_edge_network_select, param_bezier_edge_network_select) ||
          !identical(group_storage$last_modify_params_network_edges_curvature_magnitude, modify_params_network_edges_curvature_magnitude) ||
          !identical(group_storage$last_modify_params_network_edges_curvature_asymmetry, modify_params_network_edges_curvature_asymmetry) ||
          !identical(group_storage$last_modify_params_network_edges_rotate_curvature, modify_params_network_edges_rotate_curvature) ||
          modify_param_network_edge_x_shift != 0 || modify_param_network_edge_y_shift != 0

        if (is.na(params_bezier_edge_network_changed))  params_bezier_edge_network_changed <- FALSE

        param_nodelabel_network_changed <- !identical(group_storage$last_param_nodelabel_network_select, param_nodelabel_network_select) ||
          !identical(group_storage$last_modify_params_nodelabel_network_color, modify_params_nodelabel_network_color) ||
          !identical(group_storage$last_modify_params_nodelabel_network_size, modify_params_nodelabel_network_size) ||
          !identical(group_storage$last_modify_params_nodelabel_network_alpha, modify_params_nodelabel_network_alpha) ||
          !identical(group_storage$last_modify_params_nodelabel_network_angle, modify_params_nodelabel_network_angle) ||
          !identical(group_storage$last_modify_params_nodelabel_network_font, modify_params_nodelabel_network_font) ||
          !identical(group_storage$last_modify_params_nodelabel_network_fontface, modify_params_nodelabel_network_fontface)

        if (is.na(param_nodelabel_network_changed))  param_nodelabel_network_changed <- FALSE

        params_edgelabel_network_changed <- !identical(group_storage$last_modify_params_edgelabel_network, modify_params_edgelabel_network) ||
          !identical(group_storage$last_param_edgelabel_network_select, param_edgelabel_network_select) ||
          !identical(group_storage$last_modify_params_edgelabel_network_color, modify_params_edgelabel_network_color) ||
          !identical(group_storage$last_modify_params_edgelabel_network_fill, modify_params_edgelabel_network_fill) ||
          !identical(group_storage$last_modify_params_edgelabel_network_size, modify_params_edgelabel_network_size) ||
          !identical(group_storage$last_modify_params_edgelabel_network_alpha, modify_params_edgelabel_network_alpha) ||
          !identical(group_storage$last_modify_params_edgelabel_network_angle, modify_params_edgelabel_network_angle) ||
          !identical(group_storage$last_modify_params_edgelabel_network_font, modify_params_edgelabel_network_font) ||
          !identical(group_storage$last_modify_params_edgelabel_network_fontface, modify_params_edgelabel_network_fontface)


        if (is.na(params_edgelabel_network_changed))  params_edgelabel_network_changed <- FALSE

        params_node_network_changed <- !identical(group_storage$last_modify_params_node_network, modify_params_node_network) ||
          !identical(group_storage$last_param_node_network_select, param_node_network_select) ||
          !identical(group_storage$last_modify_params_node_network_color, modify_params_node_network_color) ||
          !identical(group_storage$last_modify_params_node_network_alpha, modify_params_node_network_alpha) ||
          !identical(group_storage$last_modify_params_node_network_shape, modify_params_node_network_shape) ||
          !identical(group_storage$last_modify_params_node_network_size, modify_params_node_network_size) ||
          !identical(group_storage$last_modify_params_node_network_border_color, modify_params_node_network_border_color) ||
          !identical(group_storage$last_modify_params_node_network_border_width, modify_params_node_network_border_width) ||
          !identical(group_storage$last_modify_params_node_network_width_height_ratio, modify_params_node_network_width_height_ratio)

        if (is.na(params_node_network_changed)) params_node_network_changed <- FALSE

        params_node_xy_network_changed <- modify_params_node_xy_network

        params_nodelabel_xy_network_changed <- modify_params_nodelabel_xy_network
        param_edge_xy_network_changed <-  modify_params_edge_xy_network
        param_edgelabel_xy_network_changed <- modify_params_edgelabel_xy_network

        params_nodelabel_text_network_changed <- !identical(group_storage$last_param_nodelabel_text_network_select, param_nodelabel_text_network_select) ||
          !identical(group_storage$last_params_nodelabel_network_nodelabel, params_nodelabel_network_nodelabel) ||
          !identical(group_storage$last_params_nodelabel_text_network_math_expression, params_nodelabel_text_network_math_expression)

        modify_params_edgelabel_text_network <- input$modify_params_edgelabel_text_network
        params_edgelabel_text_network <- input$param_edgelabel_text_network_select
        params_edgelabel_edgelabel_network <- input$modify_params_edgelabel_edgelabel_network
        params_edgelabel_text_network_math_expression <- input$modify_params_edgelabel_text_network_math_expression

        param_edgelabel_text_changed <- !identical(group_storage$last_params_edgelabel_text_network, params_edgelabel_text_network) ||
          !identical(group_storage$last_params_edgelabel_edgelabel_network, params_edgelabel_edgelabel_network) ||
          !identical(group_storage$last_params_edgelabel_text_network_math_expression, params_edgelabel_text_network_math_expression) ||
          !identical(remove_edgelabels_net, group_storage$last_remove_edgelabels_net)

        if (is.na(params_nodelabel_text_network_changed)) params_nodelabel_text_network_changed <- FALSE
        if (is.na(param_edgelabel_text_changed)) param_edgelabel_text_changed <- FALSE

        if (layout_changed) {
          values$group_storage$network[[group_id]]$last_layout_method <- layout_method

          if (layout_method == "dim_reduction") {
            values$group_storage$network[[group_id]]$last_dim_reduction_method <- dim_reduction_method
            showNotification(
              paste("Layout changed to Dimensionality Reduction using", dim_reduction_method, "method."),
              type = "message",
              duration = 5
            )
          } else {
            values$group_storage$network[[group_id]]$last_dim_reduction_method <- NULL
            showNotification(
              paste("Layout changed to", layout_method, "method."),
              type = "message",
              duration = 5
            )
          }
        }

        modifications_network <- values$group_storage$modifications_network[[group_id]]

        if (modify_params_edge_network == TRUE && !is.null(param_edge_network_select) && length(param_edge_network_select) > 0) {
          # Split all edges at once
          edge_specs <- strsplit(param_edge_network_select, " to ")

          # Create data frame for all network edges
          new_mod_edges_network <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            color = modify_params_edge_network_color,
            width = modify_params_edge_network_linewidth,
            alpha = modify_params_edge_network_alpha,
            line_style = modify_params_edge_network_line_style,
            color_type = modify_params_edge_network_color_type,
            end_color = modify_params_edge_network_end_color,
            gradient_position = modify_params_edge_network_gradient_position,
            stringsAsFactors = FALSE
          )

          modifications_network$edge <- rbind(
            modifications_network$edge,
            new_mod_edges_network
          )
        }

        if (modify_params_node_network == TRUE && !is.null(param_node_network_select) && length(param_node_network_select) > 0) {
          new_node_mod <- data.frame(
            text = param_node_network_select,
            color = modify_params_node_network_color,
            alpha = modify_params_node_network_alpha,
            shape = modify_params_node_network_shape,
            size = modify_params_node_network_size,
            border_color = modify_params_node_network_border_color,
            border_width = modify_params_node_network_border_width,
            width_height_ratio = modify_params_node_network_width_height_ratio,
            stringsAsFactors = FALSE
          )

          modifications_network$node <- rbind(modifications_network$node, new_node_mod)

        }

        if (modify_params_edgelabel_network == TRUE && !is.null(param_edgelabel_network_select) && length(param_edgelabel_network_select) > 0) {
          # Split all edges at once
          edge_specs <- strsplit(param_edgelabel_network_select, " to ")

          # Create data frame for all network edge labels
          new_mod_edgelabels_network <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            color = modify_params_edgelabel_network_color,
            fill = modify_params_edgelabel_network_fill,
            size = modify_params_edgelabel_network_size,
            alpha = modify_params_edgelabel_network_alpha,
            angle = modify_params_edgelabel_network_angle,
            font = modify_params_edgelabel_network_font,
            fontface = modify_params_edgelabel_network_fontface,
            stringsAsFactors = FALSE
          )

          modifications_network$edgelabel <- rbind(
            modifications_network$edgelabel,
            new_mod_edgelabels_network
          )
        }


        if (modify_params_edgelabel_xy_network == TRUE && !is.null(param_edgelabel_xy_network_select) && length(param_edgelabel_xy_network_select) > 0) {
          # Split all edges at once
          edge_specs <- strsplit(param_edgelabel_xy_network_select, " to ")

          # Create data frame for all network edge label positions
          new_mod_edgelabels_xy_network <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            x_shift = modify_params_edgelabel_network_shift_x,
            y_shift = modify_params_edgelabel_network_shift_y,
            stringsAsFactors = FALSE
          )

          modifications_network$edgelabel_xy <- rbind(
            modifications_network$edgelabel_xy,
            new_mod_edgelabels_xy_network
          )
        }


        if (modify_params_node_xy_network == TRUE && !is.null(param_node_xy_network_select) && length(param_node_xy_network_select) > 0) {
          new_mod_label <- data.frame(
            text = param_node_xy_network_select,
            x_shift = network_node_shift_x,
            y_shift = network_node_shift_y,
            stringsAsFactors = FALSE
          )

          modifications_network$node_xy <- rbind(
            modifications_network$node_xy,
            new_mod_label
          )

        }


        if (modify_params_nodelabel_network == TRUE && !is.null(param_nodelabel_network_select) && length(param_nodelabel_network_select) > 0) {

          new_mod_label <- data.frame(
            text = param_nodelabel_network_select,
            color = modify_params_nodelabel_network_color,
            size = modify_params_nodelabel_network_size,
            alpha = modify_params_nodelabel_network_alpha,
            angle = modify_params_nodelabel_network_angle,
            font = modify_params_nodelabel_network_font,
            fontface = modify_params_nodelabel_network_fontface,
            stringsAsFactors = FALSE
          )

          modifications_network$nodelabel <- rbind(
            modifications_network$nodelabel,
            new_mod_label
          )

        }


        if (modify_params_nodelabel_xy_network == TRUE && !is.null(param_nodelabel_xy_network_select) && length(param_nodelabel_xy_network_select) > 0) {

          new_mod_label <- data.frame(
            text = param_nodelabel_xy_network_select,
            x_shift = modify_params_nodelabel_network_shift_x,
            y_shift = modify_params_nodelabel_network_shift_y,
            stringsAsFactors = FALSE
          )

          modifications_network$nodelabel_xy <- rbind(
            modifications_network$nodelabel_xy,
            new_mod_label
          )

        }

        if (modify_params_nodelabel_text_network == TRUE && !is.null(param_nodelabel_text_network_select) && length(param_nodelabel_text_network_select) > 0) {
          new_mod_label <- data.frame(
            text = param_nodelabel_text_network_select,
            nodelabel = params_nodelabel_network_nodelabel,
            math_expression = params_nodelabel_text_network_math_expression,
            stringsAsFactors = FALSE
          )

          modifications_network$nodelabel_text <- rbind(
            modifications_network$nodelabel_text,
            new_mod_label
          )

        }


        if (modify_params_bezier_network_edges == TRUE && !is.null(param_bezier_edge_network_select) && length(param_bezier_edge_network_select) > 0) {
          # Split all edges at once
          edge_specs <- strsplit(param_bezier_edge_network_select, " to ")

          # Create data frame for all network bezier edges
          new_mod_bezier_edges_network <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            curvature_magnitude = modify_params_network_edges_curvature_magnitude,
            rotate_curvature = modify_params_network_edges_rotate_curvature,
            curvature_asymmetry = modify_params_network_edges_curvature_asymmetry,
            x_shift = modify_param_network_edge_x_shift,
            y_shift = modify_param_network_edge_y_shift,
            stringsAsFactors = FALSE
          )

          modifications_network$edge_curvature <- rbind(
            modifications_network$edge_curvature,
            new_mod_bezier_edges_network
          )
        }

        if (modify_params_edge_xy_network == TRUE && !is.null(param_edge_xy_network_select) && length(param_edge_xy_network_select) > 0) {
          # Split all edges at once
          edge_specs <- strsplit(param_edge_xy_network_select, " to ")

          # Create data frame for all network edge positions
          new_mod_edge_xy_network <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            start_x_shift = modify_params_edge_network_start_shift_x,
            start_y_shift = modify_params_edge_network_start_shift_y,
            end_x_shift = modify_params_edge_network_end_shift_x,
            end_y_shift = modify_params_edge_network_end_shift_y,
            stringsAsFactors = FALSE
          )

          modifications_network$edge_xy <- rbind(
            modifications_network$edge_xy,
            new_mod_edge_xy_network
          )
        }

        if (modify_params_edgelabel_text_network == TRUE && !is.null(modify_params_edgelabel_text_network) && length(modify_params_edgelabel_text_network) > 0) {
          edge_specs <- strsplit(params_edgelabel_text_network, " to ")

          new_edge_label_text <- data.frame(
            lhs = sapply(edge_specs, `[`, 1),
            op = 'to',
            rhs = sapply(edge_specs, `[`, 2),
            text = params_edgelabel_edgelabel_network,
            math_expression = params_edgelabel_text_network_math_expression,
            stringsAsFactors = FALSE
          )

          modifications_network$edgelabel_text <- rbind(
            modifications_network$edgelabel_text,
            new_edge_label_text
          )
        }

        temp_net_mod <- values$group_storage$modifications_network
        temp_net_mod[[group_id]] <- modifications_network
        values$group_storage$modifications_network <- temp_net_mod

        apply_global_nodes <- FALSE
        apply_global_edges <- FALSE
        apply_global_annotations <- FALSE

        if (inherits(bundleObject, c("qgraph"))) {

          apply_global_nodes = input$nonselective_modify_node_network_qgraph
          apply_global_edges = input$nonselective_modify_edge_network_qgraph
          apply_global_annotations = input$nonselective_modify_annotation_network_qgraph

          network_graph <- generate_graph_from_qgraph(network_object = bundleObject,
                                                      directed = is_directed,
                                                      layout_width = layout_x_net,
                                                      layout_height = layout_y_net,
                                                      x_center = x_center_net,
                                                      y_center = y_center_net,
                                                      node_shape = node_shape_net,
                                                      node_size = node_size_net,
                                                      node_alpha = node_alpha_net,
                                                      node_fill_color = node_fill_color_net,
                                                      node_border_color = node_border_color_net,
                                                      node_border_width = node_border_width_net,
                                                      node_width_height_ratio = node_width_height_ratio_net,
                                                      line_width = line_width_net,
                                                      line_color = line_color_net,
                                                      line_alpha = line_alpha_net,
                                                      min_edge_width = ifelse(scale_edge_width, min_edge_width, NULL),
                                                      max_edge_width = ifelse(scale_edge_width, max_edge_width, NULL),
                                                      scale_by_weight = scale_edge_width,
                                                      line_endpoint_spacing = line_endpoint_spacing,
                                                      arrow_type = arrow_type_net,
                                                      arrow_size = arrow_size_net,
                                                      node_label_font = node_label_font,
                                                      node_label_size = node_label_size,
                                                      node_label_color = node_label_color,
                                                      node_label_alpha = node_label_alpha,
                                                      node_label_fontface = node_label_fontface,
                                                      edge_label_font = edge_label_font,
                                                      edge_label_size = edge_label_size,
                                                      edge_label_color = edge_label_color,
                                                      edge_label_fill = edge_label_fill,
                                                      edge_label_alpha = edge_label_alpha,
                                                      edge_label_fontface = edge_label_fontface,
                                                      zoom_factor = input$zoom,
                                                      data_file = NULL,
                                                      bezier_network_edges = bezier_network_edges,
                                                      network_edges_curvature_magnitude = network_edges_curvature_magnitude,
                                                      network_edges_rotate_curvature = network_edges_rotate_curvature,
                                                      network_edges_curvature_asymmetry = network_edges_curvature_asymmetry,
                                                      use_clustering = use_clustering,
                                                      clustering_method = clustering_method,
                                                      cluster_palette = cluster_palette,
                                                      modify_params_edge = ifelse (!is.null(modifications_network$edge), TRUE, FALSE),
                                                      modified_edges = modifications_network$edge,
                                                      modify_params_edgelabel = ifelse (!is.null(modifications_network$edgelabel), TRUE, FALSE),
                                                      modified_edgelabels = modifications_network$edgelabel,
                                                      modify_params_edgelabel_xy = ifelse (!is.null(modifications_network$edgelabel_xy), TRUE, FALSE),
                                                      modified_edgelabels_xy = modifications_network$edgelabel_xy,
                                                      modify_params_node = ifelse (!is.null(modifications_network$node), TRUE, FALSE),
                                                      modified_nodes = modifications_network$node,
                                                      modify_params_node_xy = ifelse (!is.null(modifications_network$node_xy), TRUE, FALSE),
                                                      modified_nodes_xy = modifications_network$node_xy,
                                                      modify_params_nodelabel = ifelse (!is.null(modifications_network$nodelabel), TRUE, FALSE),
                                                      modified_nodelabels = modifications_network$nodelabel,
                                                      modify_params_nodelabel_xy = ifelse (!is.null(modifications_network$nodelabel_xy), TRUE, FALSE),
                                                      modified_nodelabels_xy = modifications_network$nodelabel_xy,
                                                      modify_params_nodelabel_text = ifelse (!is.null(modifications_network$nodelabel_text), TRUE, FALSE),
                                                      modified_nodelabels_text = modifications_network$nodelabel_text,
                                                      modify_params_bezier_edge = ifelse (!is.null(modifications_network$edge_curvature), TRUE, FALSE),
                                                      modified_bezier_edges = modifications_network$edge_curvature,
                                                      modify_params_edge_xy = ifelse (!is.null(modifications_network$edge_xy), TRUE, FALSE),
                                                      modified_edges_xy = modifications_network$edge_xy,
                                                      modify_params_edgelabel_text = ifelse (!is.null(modifications_network$edgelabel_text), TRUE, FALSE),
                                                      modified_edgelabels_text = modifications_network$edgelabel_text,
                                                      apply_global_nodes = apply_global_nodes,
                                                      apply_global_edges = apply_global_edges,
                                                      apply_global_annotations = apply_global_annotations,
                                                      flip_layout = flip_network_layout,
                                                      flip_direction = flip_network_layout_direction,
                                                      rotate_layout = rotate_network_layout,
                                                      rotate_angle = rotate_network_layout_angle,
                                                      which_group = group_id)

        } else {
          # igraph or no network object

          network_prep <- generate_network_layout(network_object = bundleObject,
                                                  edges = group_storage$edges,
                                                  nodes = group_storage$nodes,
                                                  layout_method = ifelse(layout_method == 'layout_ai', "fr", layout_method),
                                                  directed = is_directed,
                                                  dim_reduction_method = dim_reduction_method,
                                                  random_seed = random_seed,
                                                  flip_layout = flip_network_layout,
                                                  flip_direction = flip_network_layout_direction,
                                                  rotate_layout = rotate_network_layout,
                                                  rotate_angle = rotate_network_layout_angle)

          if (group_storage$last_layout_method == 'layout_ai' && !layout_changed) {
            layout <- group_storage$layout # layout by AI
          } else if (group_storage$last_layout_method != 'layout_ai' && layout_method == 'layout_ai') {

            if (input$ai_model %in% c("gemini", "openai", "mistral", "claude")) {
              api_key <- switch(
                input$ai_model,
                "gemini" = input$gemini_api_key,
                "openai" = input$openai_api_key,
                "mistral" = input$mistral_api_key,
                "claude" = input$claude_api_key
              )

              tryCatch({
                validate_api_key(api_key, input$ai_model)
              },
              error = function(e) {
                showNotification(
                  paste("API key validation failed for", input$ai_model, ":", e$message),
                  type = "error"
                )
                return(NULL)
              })
            }

            ai_settings <- list(
              gemini = input$gemini_api_key,
              openai = input$openai_api_key,
              mistral = input$mistral_api_key,
              claude = input$claude_api_key,
              ollama_model = input$ollama_model
            )
            showModal(modalDialog(
              title = paste("Generating network layout with", input$ai_model, "..."),
              "Please wait while AI generates network layout",
              footer = NULL,
              easyClose = FALSE
            ))

            prompt <- create_network_layout_prompt(network_prep$layout, comments = input$additional_prompts_network_layout)
            ai_response <- tryCatch({
              call_selected_ai(prompt, input$ai_model, ai_settings)
            }, error = function(e) {
              removeModal()
              showNotification(
                paste("Error calling AI model:", e$message),
                type = "error"
              )
              return(NULL)
            })

            if (!is.null(ai_response)) {
              layout <- tryCatch({
                parse_ai_layout_response(ai_response)
              }, error = function(e) {
                showNotification(
                  paste("Error parsing AI response:", e$message),
                  type = "error"
                )
                return(layout) # keep old coords if parsing fails
              })

              removeModal()
              showNotification("Network layout generated successfully with GenAI!", type = "message")
            }

          } else {
            layout <- network_prep$layout
          }

          multi_group_network_layout <- input$multi_group_network_layout

          if (multi_group_network_layout) {
            group_to_modify <- as.character(input$modify_network_group_select)
            group_to_match <- as.character(input$modify_network_group_match)

            group_settings_modify <- values$group_storage$network[[group_to_modify]]
            group_settings_match <- values$group_storage$network[[group_to_match]]

            layout_method <- group_settings_match$last_layout_method
            nodes_modify <- group_settings_modify$nodes$node
            nodes_match <- group_settings_match$nodes$node

            same_nodes <- setequal(nodes_modify, nodes_match)

            if (same_nodes) {
              layout_match <- group_settings_match$layout
              layout_match_index <- match(nodes_modify, layout_match$node)

              if (!any(is.na(layout_match_index))) {
                layout <- layout_match[layout_match_index, ]
                rownames(layout) <- NULL
                layout_changed <- TRUE
              } else {
                layout_changed <- FALSE
              }

            } else {
              layout_changed <- FALSE
            }

            if (group_settings_match$last_layout_method == 'layout_ai')  {
              values$group_storage$network[[group_to_modify]]$last_layout_method <- layout_method
              layout_changed <- FALSE
            }

          } else {
            layout_method <- input$layout_method
            layout_changed <- is.null(values$group_storage$network[[group_id]]$last_layout_method) ||
              !identical(layout_method, values$group_storage$network[[group_id]]$last_layout_method)
          }

          if (network_prep$is_bipartite) {
            change_group_node_bipartite <- input$change_group_node_bipartite
            change_group_edge_bipartite <- input$change_group_edge_bipartite
            change_group_annotation_bipartite <- input$change_group_annotation_bipartite
          }

          network_graph <- generate_graph_from_network(
            graph = network_prep$graph,
            layout = layout,
            is_bipartite = network_prep$is_bipartite,
            edges = group_storage$edges,
            nodes = group_storage$nodes,
            directed = is_directed,
            layout_width = layout_x_net,
            layout_height = layout_y_net,
            x_center = x_center_net,
            y_center = y_center_net,
            node_shape = node_shape_net,
            node_size = node_size_net,
            node_alpha = node_alpha_net,
            node_fill_color = node_fill_color_net,
            node_border_color = node_border_color_net,
            node_border_width = node_border_width_net,
            node_width_height_ratio = node_width_height_ratio_net,
            line_width = line_width_net,
            line_color = line_color_net,
            line_alpha = line_alpha_net,
            min_edge_width = ifelse(scale_edge_width, min_edge_width, NULL),
            max_edge_width = ifelse(scale_edge_width, max_edge_width, NULL),
            scale_by_weight = scale_edge_width,
            line_endpoint_spacing = line_endpoint_spacing,
            arrow_type = arrow_type_net,
            arrow_size = arrow_size_net,
            node_label_font = node_label_font,
            node_label_size = node_label_size,
            node_label_color = node_label_color,
            node_label_alpha = node_label_alpha,
            node_label_fontface = node_label_fontface,
            edge_label_font = edge_label_font,
            edge_label_size = edge_label_size,
            edge_label_color = edge_label_color,
            edge_label_fill = edge_label_fill,
            edge_label_alpha = edge_label_alpha,
            edge_label_fontface = edge_label_fontface,
            zoom_factor = input$zoom,
            annotate_nodes = TRUE,
            annotate_edges = TRUE,
            remove_edgelabels = remove_edgelabels_net,
            use_clustering = use_clustering,
            clustering_method = clustering_method,
            cluster_palette = cluster_palette,
            bezier_network_edges = bezier_network_edges,
            network_edges_curvature_magnitude = network_edges_curvature_magnitude,
            network_edges_rotate_curvature = network_edges_rotate_curvature,
            network_edges_curvature_asymmetry = network_edges_curvature_asymmetry,
            modify_params_edge = ifelse (!is.null(modifications_network$edge), TRUE, FALSE),
            modified_edges = modifications_network$edge,
            modify_params_edgelabel = ifelse (!is.null(modifications_network$edgelabel), TRUE, FALSE),
            modified_edgelabels = modifications_network$edgelabel,
            modify_params_edgelabel_xy = ifelse (!is.null(modifications_network$edgelabel_xy), TRUE, FALSE),
            modified_edgelabels_xy = modifications_network$edgelabel_xy,
            modify_params_node = ifelse (!is.null(modifications_network$node), TRUE, FALSE),
            modified_nodes = modifications_network$node,
            modify_params_node_xy = ifelse (!is.null(modifications_network$node_xy), TRUE, FALSE),
            modified_nodes_xy = modifications_network$node_xy,
            modify_params_nodelabel = ifelse (!is.null(modifications_network$nodelabel), TRUE, FALSE),
            modified_nodelabels = modifications_network$nodelabel,
            modify_params_nodelabel_xy = ifelse (!is.null(modifications_network$nodelabel_xy), TRUE, FALSE),
            modified_nodelabels_xy = modifications_network$nodelabel_xy,
            modify_params_nodelabel_text = ifelse (!is.null(modifications_network$nodelabel_text), TRUE, FALSE),
            modified_nodelabels_text = modifications_network$nodelabel_text,
            modify_params_bezier_edge = ifelse (!is.null(modifications_network$edge_curvature), TRUE, FALSE),
            modified_bezier_edges = modifications_network$edge_curvature,
            modify_params_edge_xy = ifelse (!is.null(modifications_network$edge_xy), TRUE, FALSE),
            modified_edges_xy = modifications_network$edge_xy,
            modify_params_edgelabel_text = ifelse (!is.null(modifications_network$edgelabel_text), TRUE, FALSE),
            modified_edgelabels_text = modifications_network$edgelabel_text,
            apply_bipartite_nodes = change_group_node_bipartite,
            apply_bipartite_edges = change_group_edge_bipartite,
            apply_bipartite_annotations = change_group_annotation_bipartite,
            last_state = last_saved_state(),
            which_group = group_id
          )
        }

        values$group_storage$network[[group_id]]$layout <- layout

        if (is_bipartite) {
          values$group_storage$network[[group_id]]$last_apply_bipartite_nodes <- change_group_node_bipartite
          values$group_storage$network[[group_id]]$last_apply_bipartite_edges <- change_group_edge_bipartite
          values$group_storage$network[[group_id]]$last_apply_bipartite_annotations <- change_group_annotation_bipartite
        }

        node_fill_color_net_gradient <- input$node_fill_color_net_gradient
        gradient_position_points_net <- input$gradient_position_points_net
        grad_start_color_net <- input$grad_start_color_net
        grad_end_color_net <- input$grad_end_color_net


        if (node_fill_color_net_gradient) {

          n_unlocked_points <- nrow(network_graph$points)
          split_index <- round(gradient_position_points_net * n_unlocked_points)

          grad_start_color <- grad_start_color_net
          grad_end_color <- grad_end_color_net

          if (n_unlocked_points > 1) {

            color_interpolator <- colorRampPalette(c(grad_start_color, grad_end_color))
            intermediate_color <- color_interpolator(3)[2]

            gradient_colors_start <- colorRampPalette(c(grad_start_color, intermediate_color))(split_index)
            gradient_colors_end <- colorRampPalette(c(intermediate_color, grad_end_color))(n_unlocked_points - split_index)

            gradient_colors_layout <- c(gradient_colors_start,gradient_colors_end)

            network_graph$points$color <- gradient_colors_layout

          }
        }

        values$group_storage$network[[group_id]]$last_node_fill_color_net_gradient <- node_fill_color_net_gradient
        values$group_storage$network[[group_id]]$last_grad_start_color_net <- grad_start_color_net
        values$group_storage$network[[group_id]]$last_grad_end_color_net <- grad_end_color_net
        values$group_storage$network[[group_id]]$last_gradient_position_points_net <- gradient_position_points_net

        values$group_storage$network[[group_id]]$last_apply_global_nodes <-
          if (!is.null(apply_global_nodes) && exists("apply_global_nodes")) {
            apply_global_nodes
          } else {
            FALSE
          }

        values$group_storage$network[[group_id]]$last_apply_global_edges <-
          if (!is.null(apply_global_edges) && exists("apply_global_edges")) {
            apply_global_edges
          } else {
            FALSE
          }

        values$group_storage$network[[group_id]]$last_apply_global_annotations <-
          if (!is.null(apply_global_annotations) && exists("apply_global_annotations")) {
            apply_global_annotations
          } else {
            FALSE
          }

        # Layout
        values$group_storage$network[[group_id]]$last_layout_method <- layout_method
        values$group_storage$network[[group_id]]$last_layout_x <- layout_x_net
        values$group_storage$network[[group_id]]$last_layout_y <- layout_y_net
        values$group_storage$network[[group_id]]$last_random_seed <- random_seed
        values$group_storage$network[[group_id]]$last_x_center <- x_center_net
        values$group_storage$network[[group_id]]$last_y_center <- y_center_net
        values$group_storage$network[[group_id]]$last_flip_network_layout <- flip_network_layout
        values$group_storage$network[[group_id]]$last_flip_network_layout_direction <- flip_network_layout_direction
        values$group_storage$network[[group_id]]$last_rotate_network_layout <- rotate_network_layout
        values$group_storage$network[[group_id]]$last_rotate_network_layout_angle <- rotate_network_layout_angle

        values$group_storage$network[[group_id]]$last_dim_reduction_method <- dim_reduction_method
        values$group_storage$network[[group_id]]$last_is_directed <- is_directed
        values$group_storage$network[[group_id]]$last_use_clustering <- use_clustering
        values$group_storage$network[[group_id]]$last_clustering_method <- clustering_method
        values$group_storage$network[[group_id]]$last_cluster_palette <- cluster_palette

        # Node properties
        values$group_storage$network[[group_id]]$last_node_shape <- node_shape_net
        values$group_storage$network[[group_id]]$last_node_size <- node_size_net
        values$group_storage$network[[group_id]]$last_node_alpha <- node_alpha_net
        values$group_storage$network[[group_id]]$last_node_fill_color <- node_fill_color_net
        values$group_storage$network[[group_id]]$last_node_border_color <- node_border_color_net
        values$group_storage$network[[group_id]]$last_node_border_width <- node_border_width_net
        values$group_storage$network[[group_id]]$last_node_width_height_ratio <- node_width_height_ratio_net

        # Edge/Line properties
        values$group_storage$network[[group_id]]$last_line_width <- line_width_net
        values$group_storage$network[[group_id]]$last_line_color <- line_color_net
        values$group_storage$network[[group_id]]$last_line_alpha <- line_alpha_net
        values$group_storage$network[[group_id]]$last_min_edge_width <- min_edge_width
        values$group_storage$network[[group_id]]$last_max_edge_width <- max_edge_width
        values$group_storage$network[[group_id]]$last_scale_edge_width <- scale_edge_width
        values$group_storage$network[[group_id]]$last_line_endpoint_spacing <- line_endpoint_spacing
        values$group_storage$network[[group_id]]$last_bezier_network_edges <- bezier_network_edges
        values$group_storage$network[[group_id]]$last_network_edges_curvature_magnitude <- network_edges_curvature_magnitude
        values$group_storage$network[[group_id]]$last_network_edges_curvature_asymmetry <- network_edges_curvature_asymmetry

        # Arrow properties
        values$group_storage$network[[group_id]]$last_arrow_type <- arrow_type_net
        values$group_storage$network[[group_id]]$last_arrow_size <- arrow_size_net

        # Node label properties
        values$group_storage$network[[group_id]]$last_node_label_font <- node_label_font
        values$group_storage$network[[group_id]]$last_node_label_size <- node_label_size
        values$group_storage$network[[group_id]]$last_node_label_color <- node_label_color
        values$group_storage$network[[group_id]]$last_node_label_alpha <- node_label_alpha
        values$group_storage$network[[group_id]]$last_node_label_fontface <- node_label_fontface

        # Edge label properties
        values$group_storage$network[[group_id]]$last_edge_label_font <- edge_label_font
        values$group_storage$network[[group_id]]$last_edge_label_size <- edge_label_size
        values$group_storage$network[[group_id]]$last_edge_label_color <- edge_label_color
        values$group_storage$network[[group_id]]$last_apply_edge_label_fill <- apply_edge_label_fill
        values$group_storage$network[[group_id]]$last_edge_label_fill <- edge_label_fill
        values$group_storage$network[[group_id]]$last_edge_label_alpha <- edge_label_alpha
        values$group_storage$network[[group_id]]$last_edge_label_fontface <- edge_label_fontface
        values$group_storage$network[[group_id]]$last_annotate_edges <- annotate_edges
        values$group_storage$network[[group_id]]$last_network_edges_rotate_curvature <- network_edges_rotate_curvature
        values$group_storage$network[[group_id]]$last_remove_edgelabels_net <- remove_edgelabels_net


        values$group_storage$network[[group_id]]$last_modify_params_edge_network <- modify_params_edge_network
        if (modify_params_edge_network == TRUE && !is.null(param_edge_network_select) && length(param_edge_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_param_edge_network_select <- param_edge_network_select
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_color <- modify_params_edge_network_color
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_linewidth <- modify_params_edge_network_linewidth
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_alpha <- modify_params_edge_network_alpha
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_line_style <- modify_params_edge_network_line_style
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_color_type <- modify_params_edge_network_color_type
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_end_color <- modify_params_edge_network_end_color
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_gradient_position <- modify_params_edge_network_gradient_position
        }

        values$group_storage$network[[group_id]]$last_modify_params_node_network <- modify_params_node_network
        if (modify_params_node_network == TRUE && !is.null(param_node_network_select) && length(param_node_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_param_node_network_select <- param_node_network_select
          values$group_storage$network[[group_id]]$last_modify_params_node_network_color <- modify_params_node_network_color
          values$group_storage$network[[group_id]]$last_modify_params_node_network_alpha <- modify_params_node_network_alpha
          values$group_storage$network[[group_id]]$last_modify_params_node_network_shape <- modify_params_node_network_shape
          values$group_storage$network[[group_id]]$last_modify_params_node_network_border_color <- modify_params_node_network_border_color
          values$group_storage$network[[group_id]]$last_modify_params_node_network_border_width <- modify_params_node_network_border_width
          values$group_storage$network[[group_id]]$last_modify_params_node_network_size <- modify_params_node_network_size
          values$group_storage$network[[group_id]]$last_modify_params_node_network_width_height_ratio <- modify_params_node_network_width_height_ratio
        }

        if (modify_params_nodelabel_text_network == TRUE && !is.null(param_nodelabel_text_network_select) && length(param_nodelabel_text_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_param_nodelabel_text_network_select <- param_nodelabel_text_network_select
          values$group_storage$network[[group_id]]$last_params_nodelabel_network_nodelabel <- params_nodelabel_network_nodelabel
          values$group_storage$network[[group_id]]$last_params_nodelabel_text_network_math_expression <- params_nodelabel_text_network_math_expression
        }


        values$group_storage$network[[group_id]]$last_modify_params_nodelabel_network <- modify_params_nodelabel_network
        if (modify_params_nodelabel_network == TRUE && !is.null(param_nodelabel_network_select) && length(param_nodelabel_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_param_nodelabel_network_select <- param_nodelabel_network_select
          values$group_storage$network[[group_id]]$last_modify_params_nodelabel_network_color <- modify_params_nodelabel_network_color
          values$group_storage$network[[group_id]]$last_modify_params_nodelabel_network_size <- modify_params_nodelabel_network_size
          values$group_storage$network[[group_id]]$last_modify_params_nodelabel_network_alpha <- modify_params_nodelabel_network_alpha
          values$group_storage$network[[group_id]]$last_modify_params_nodelabel_network_angle <- modify_params_nodelabel_network_angle
          values$group_storage$network[[group_id]]$last_modify_params_nodelabel_network_font <- modify_params_nodelabel_network_font
          values$group_storage$network[[group_id]]$last_modify_params_nodelabel_network_fontface <- modify_params_nodelabel_network_fontface
        }


        values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network <- modify_params_edgelabel_network
        if (modify_params_edgelabel_network == TRUE && !is.null(param_edgelabel_network_select) && length(param_edgelabel_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_param_edgelabel_network_select <- param_edgelabel_network_select
          values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network_color <- modify_params_edgelabel_network_color
          values$group_storage$network[[group_id]]$last_apply_modify_params_edgelabel_network_fill <- apply_modify_params_edgelabel_network_fill
          values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network_fill <- modify_params_edgelabel_network_fill
          values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network_size <- modify_params_edgelabel_network_size
          values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network_alpha <- modify_params_edgelabel_network_alpha
          values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network_angle <- modify_params_edgelabel_network_angle
          values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network_font <- modify_params_edgelabel_network_font
          values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network_fontface <- modify_params_edgelabel_network_fontface
        }


        values$group_storage$network[[group_id]]$last_modify_params_bezier_network_edges <- modify_params_bezier_network_edges

        if (input$modify_params_bezier_network_edges == TRUE && !is.null(param_bezier_edge_network_select) && length(param_bezier_edge_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_param_bezier_edge_network_select <- param_bezier_edge_network_select
          values$group_storage$network[[group_id]]$last_modify_params_network_edges_curvature_magnitude <- modify_params_network_edges_curvature_magnitude
          values$group_storage$network[[group_id]]$last_modify_params_network_edges_rotate_curvature <- modify_params_network_edges_rotate_curvature
          values$group_storage$network[[group_id]]$last_modify_params_network_edges_curvature_asymmetry <- modify_params_network_edges_curvature_asymmetry
          values$group_storage$network[[group_id]]$last_modify_param_network_edge_x_shift <- modify_param_network_edge_x_shift
          values$group_storage$network[[group_id]]$last_modify_param_network_edge_y_shift <- modify_param_network_edge_y_shift
        }

        values$group_storage$network[[group_id]]$last_modify_params_node_xy_network <- modify_params_node_xy_network
        if (modify_params_node_xy_network == TRUE && !is.null(param_node_xy_network_select) && length(param_node_xy_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_modify_params_node_shift_x <- network_node_shift_x
          values$group_storage$network[[group_id]]$last_modify_params_node_shift_y <- network_node_shift_y
          values$group_storage$network[[group_id]]$last_param_node_xy_network_select <- param_node_xy_network_select
        }

        values$group_storage$network[[group_id]]$last_modify_params_edge_xy_network <- modify_params_edge_xy_network
        if (modify_params_edge_xy_network == TRUE && !is.null(param_edge_xy_network_select) && length(param_edge_xy_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_start_shift_x <- modify_params_edge_network_start_shift_x
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_start_shift_y <- modify_params_edge_network_start_shift_y
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_end_shift_x = modify_params_edge_network_end_shift_x
          values$group_storage$network[[group_id]]$last_modify_params_edge_network_end_shift_y = modify_params_edge_network_end_shift_y
          values$group_storage$network[[group_id]]$last_param_edge_xy_network_select <- param_edge_xy_network_select
        }

        values$group_storage$network[[group_id]]$last_modify_params_nodelabel_xy_network <- modify_params_nodelabel_xy_network
        if (modify_params_nodelabel_xy_network == TRUE && !is.null(param_nodelabel_xy_network_select) && length(param_nodelabel_xy_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_modify_params_nodelabel_network_shift_x <- modify_params_nodelabel_network_shift_x
          values$group_storage$network[[group_id]]$last_modify_params_nodelabel_network_shift_y <- modify_params_nodelabel_network_shift_y
          values$group_storage$network[[group_id]]$last_param_nodelabel_xy_network_select <- param_nodelabel_xy_network_select
        }

        values$group_storage$network[[group_id]]$last_param_edgelabel_xy_network_select <- modify_params_edgelabel_xy_network
        if (modify_params_edgelabel_xy_network == TRUE && !is.null(param_edgelabel_xy_network_select) && length(param_edgelabel_xy_network_select) > 0) {
          values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network_shift_y <- modify_params_edgelabel_network_shift_x
          values$group_storage$network[[group_id]]$last_modify_params_edgelabel_network_shift_y <- modify_params_edgelabel_network_shift_y
          values$group_storage$network[[group_id]]$last_param_edgelabel_xy_network_select <- param_edgelabel_xy_network_select
        }

        values$group_storage$network[[group_id]]$last_modify_params_edgelabel_text_network <- modify_params_edgelabel_text_network
        if (modify_params_edgelabel_text_network == TRUE && !is.null(params_edgelabel_text_network) && length(params_edgelabel_text_network) > 0) {
          values$group_storage$sem[[group_id]]$last_params_edgelabel_text_network <- params_edgelabel_text_network
          values$group_storage$sem[[group_id]]$last_params_edgelabel_edgelabel_network <- params_edgelabel_edgelabel_network
          values$group_storage$sem[[group_id]]$last_params_edgelabel_text_network_math_expression <- params_edgelabel_text_network_math_expression
        }

        network_points <- which(values$points$network == TRUE & values$points$group == group_id)
        if (length(network_points) > 0) {
          values$points[network_points, ] <- network_graph$points
        }

        network_lines <- which(values$lines$network == TRUE & values$lines$group == group_id)
        if (length(network_lines) > 0) {
          values$lines[network_lines, ] <- network_graph$lines
        }

        network_annotations <- which(values$annotations$network == TRUE & values$annotations$group == group_id)

        if (length(network_annotations) > 0) {
          original_xy <- values$annotations[network_annotations, c('x', 'y')]

          values$annotations$temp_order <- 1:nrow(values$annotations)

          annotations_etc <- values$annotations[-network_annotations, ]

          if (nrow(network_graph$annotations) > 0) {
            if (nrow(network_graph$annotations) <= length(network_annotations)) {
              new_positions <- network_annotations[1:nrow(network_graph$annotations)]
            } else {
              new_positions <- c(
                network_annotations,
                (max(values$annotations$temp_order) + 1):(max(values$annotations$temp_order) +
                                                            nrow(network_graph$annotations) - length(network_annotations))
              )
            }

            network_graph$annotations$temp_order <- new_positions

            values$annotations <- rbind(annotations_etc, network_graph$annotations)
            values$annotations <- values$annotations[order(values$annotations$temp_order), ]

          } else {
            values$annotations <- annotations_etc
          }

          values$annotations$temp_order <- NULL

          no_changes <- !any(
            layout_changed, layout_flip_changed, layout_angle_changed, scaling_changed,
            positional_shift, random_seed_changed, bezier_shift, asymmetry_changed,
            params_node_xy_network_changed, params_node_network_changed, params_edge_network_changed,
            params_edgelabel_network_changed, param_edgelabel_xy_network_changed,
            params_bezier_edge_network_changed, params_nodelabel_xy_network_changed,
            params_nodelabel_text_network_changed, param_edge_xy_network_changed,
            param_edgelabel_text_changed
          )

          if (no_changes) {
            values$annotations[network_annotations, c('x', 'y')] <- original_xy
          }
        }

        network_change_click$n_count <- network_change_click$n_count + 1

        output$plot <- renderPlot({
          recreate_plot()
        })

        showNotification(
          HTML(paste("Successfully updated", "<b>network</b>", "visualization in group", "<b>", group_id, "</b>")),
          type = "message"
        )
      }
    }, error = function(e) {
      showNotification(
        paste("Error applying network changes:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  observeEvent(input$delete_everything, {
    save_state()
    values$points <- data.frame(
      x = numeric(), y = numeric(), shape = character(), color = character(), size = numeric(),
      border_color = character(), border_width = numeric(), alpha = numeric(), width_height_ratio = numeric(),
      orientation = numeric(), lavaan = logical(), network = logical(), locked = logical(), group = character(), stringsAsFactors = FALSE
    )
    values$lines <- data.frame(
      x_start = numeric(), y_start = numeric(), x_end = numeric(), y_end = numeric(),
      ctrl_x = numeric(), ctrl_y = numeric(), ctrl_x2 = numeric(), ctrl_y2 = numeric(),
      curvature_magnitude = numeric(), rotate_curvature = logical(), curvature_asymmetry = numeric(),
      type = character(), color = character(), end_color = character(), color_type = character(),
      gradient_position = numeric(), width = numeric(), alpha = numeric(), arrow = logical(), arrow_type = character(),
      arrow_size = numeric(), two_way = logical(), lavaan = logical(), network = logical(), line_style = character(), locked = logical(),
      group = character(), stringsAsFactors = FALSE
    )
    values$annotations <- data.frame(
      text = character(), x = numeric(), y = numeric(), font = character(),
      size = numeric(), color = character(), fill = character(), angle = numeric(), alpha = numeric(),
      fontface = character(), math_expression = logical(), hjust = numeric(), vjust = numeric(), lavaan = logical(),
      network = logical(), locked = logical(), group_label = logical(), loop_label = logical(), group = character(),  stringsAsFactors = FALSE
    )
    values$loops <- data.frame(
      x_center = numeric(), y_center = numeric(), radius = numeric(), color = character(),
      width = numeric(), alpha = numeric(), arrow_type = character(), arrow_size = numeric(),
      gap_size = numeric(), loop_width = numeric(), loop_height = numeric(), orientation = numeric(),
      lavaan = logical(), two_way = logical(), locked = logical(), group = character(), stringsAsFactors = FALSE
    )

    # Reset model_state (SEM)
    # model_state$original <- NULL
    # model_state$current <- NULL
    # model_state$code <- NULL
    # model_state$data <- NULL

    # Reset network_state
    network_state$nodes <- NULL
    network_state$edges <- NULL
    network_state$weights <- NULL
    network_state$data <- NULL


    reset_group(1)
    # updateTextInput(session, "which_group", value = "1")

    showNotification(
      paste("The plotting space has been cleared."),
      type = "message"
    )

    output$plot <- renderPlot({
      recreate_plot()
    })
  })

  observeEvent(input$delete_all_points, {
    save_state()
    values$points <- data.frame(
      x = numeric(), y = numeric(), shape = character(), color = character(), size = numeric(),
      border_color = character(), border_width = numeric(), alpha = numeric(), width_height_ratio = numeric(),
      orientation = numeric(), lavaan = logical(), network = logical(), locked = logical(), stringsAsFactors = FALSE
    )
    showNotification(
      paste("All points have been deleted."),
      type = "message"
    )

    output$plot <- renderPlot({
      recreate_plot()
    })
  })

  observeEvent(input$delete_selected_point, {
    selected_row <- input$data_table_rows_selected
    if (!is.null(selected_row)) {
      save_state()
      values$points <- values$points[-selected_row, ]
      showNotification(
        paste("Points at rows", paste(selected_row, collapse = ", "), "have been deleted."),
        type = "message"
      )
    }


    output$plot <- renderPlot({
      recreate_plot()
    })
  })


  observeEvent(input$delete_selected_line, {
    selected_row <- input$line_table_rows_selected
    if (!is.null(selected_row)) {
      save_state()
      values$lines <- values$lines[-selected_row, ]
      showNotification(
        paste("Lines at rows", paste(selected_row, collapse = ", "), "have been deleted."),
        type = "message"
      )
    }
  })

  observeEvent(input$delete_all_lines, {
    save_state()
    values$lines <- data.frame(
      x_start = numeric(), y_start = numeric(), x_end = numeric(), y_end = numeric(),
      ctrl_x = numeric(), ctrl_y = numeric(), ctrl_x2 = numeric(), ctrl_y2 = numeric(),
      curvature_magnitude = numeric(), rotate_curvature = logical(), curvature_asymmetry = numeric(),
      type = character(), color = character(), end_color = character(), color_type = character(),
      gradient_position = numeric(), width = numeric(), alpha = numeric(), arrow = logical(), arrow_type = character(),
      arrow_size = numeric(), two_way = logical(), lavaan = logical(), network = logical(), line_style = character(), locked = logical(), stringsAsFactors = FALSE
    )
    showNotification(
      paste("All lines have been deleted."),
      type = "message"
    )
  })

  observeEvent(input$delete_selected_annotation, {
    selected_row <- input$annotation_table_rows_selected
    if (!is.null(selected_row)) {
      save_state()
      values$annotations <- values$annotations[-selected_row, ]
      showNotification(
        paste("Annotations at rows", paste(selected_row, collapse = ", "), "have been deleted."),
        type = "message"
      )
    }
  })

  observeEvent(input$lavaan_file, {
    req(input$lavaan_file)
    user_data <- tryCatch({
      read.csv(input$lavaan_file$datapath)
    }, error = function(e) {
      showNotification("Error reading CSV file. Please upload a valid CSV.", type = "error")
      return(NULL)
    })

    if (!is.null(user_data)) {
      uploaded_data(user_data)
    }
  })


  observeEvent(input$delete_all_annotations, {
    save_state()
    values$annotations <- data.frame(
      text = character(), x = numeric(), y = numeric(), font = character(),
      size = numeric(), color = character(), fill = character(), angle = numeric(), alpha = numeric(),
      fontface = character(), math_expression = logical(), hjust = numeric(), vjust = numeric(), lavaan = logical(),
      network = logical(), locked = logical(), group_label = logical(), loop_label = logical(), group = character(),  stringsAsFactors = FALSE
    )
    showNotification(
      paste("All annotations have been deleted."),
      type = "message"
    )
  })

  observeEvent(input$delete_selected_loop, {
    selected_row <- input$loop_table_rows_selected
    if (!is.null(selected_row)) {
      save_state()
      values$loops <- values$loops[-selected_row, ]
      showNotification(
        paste("Loops at rows", paste(selected_row, collapse = ", "), "have been deleted."),
        type = "message"
      )
    }
  })

  observeEvent(input$delete_all_loops, {
    save_state()
    values$loops <- data.frame(
      x_center = numeric(), y_center = numeric(), radius = numeric(), color = character(),
      width = numeric(), alpha = numeric(), arrow_type = character(), arrow_size = numeric(),
      gap_size = numeric(), loop_width = numeric(), loop_height = numeric(), orientation = numeric(),
      lavaan = logical(), two_way = logical(), locked = logical(), stringsAsFactors = FALSE
    )
    showNotification(
      paste("All self-loop arrows have been deleted."),
      type = "message"
    )
  })

  zoom_val <- reactiveVal(1.2)
  horizontal_val <- reactiveVal(0)
  vertical_val <- reactiveVal(0)
  layer_order <- debounce(reactive(input$layer_order), 300)


  observeEvent(input$change_view, {

    save_state()
    if (input$zoom_numeric >= 0.1 && input$zoom_numeric <= 10) {
      zoom_val(input$zoom_numeric)
    } else {
      updateNumericInput(session, "zoom_numeric", value = zoom_val())
    }

    if (input$horizontal_numeric >= -100 && input$horizontal_numeric <= 100) {
      horizontal_val(input$horizontal_numeric)
    } else {
      updateNumericInput(session, "horizontal_numeric", value = horizontal_val())
    }

    if (input$vertical_numeric >= -100 && input$vertical_numeric <= 100) {
      vertical_val(input$vertical_numeric)
    } else {
      updateNumericInput(session, "vertical_numeric", value = vertical_val())
    }

    showNotification("View has been changed", type = "message", duration = 5)
  })

  zoom_level <- debounce(reactive(zoom_val()), 300)
  horizontal_shift <- debounce(reactive(horizontal_val()), 300)
  vertical_shift <- debounce(reactive(vertical_val()), 300)

  memo_draw_layer <- function(layer_func) {
    memoise::memoise(
      function(data, zoom) {
        key <- digest::digest(list(data, zoom))
        layer_func(data, zoom)
      },
      cache = cachem::cache_mem(max_age = 60, max_size = 50 * 1024^2)
    )
  }

  memo_draw_points <- memo_draw_layer(draw_points)
  memo_draw_lines <- memo_draw_layer(draw_lines)
  memo_draw_annotations <- memo_draw_layer(draw_annotations)
  memo_draw_loops <- memo_draw_layer(draw_loops)


  recreate_plot <- function() {
    if (is.null(zoom_level()) || is.null(horizontal_shift()) ||
        is.null(vertical_shift()) || is.null(layer_order())) {
      return(ggplot() + theme_void() + ggtitle("Error: Missing required inputs"))
    }

    tryCatch({
      zoom_level <- zoom_level()
      horizontal_shift <- horizontal_shift()
      vertical_shift <- vertical_shift()
      current_layer_order <- layer_order()

      x_lim <- c(-40, 40) * zoom_level + horizontal_shift
      y_lim <- c(-40, 40) * zoom_level + vertical_shift

      p <- ggplot() +
        coord_fixed(
          ratio = 1,
          xlim = x_lim,
          ylim = y_lim,
          expand = FALSE,
          clip = "off"
        ) +
        theme_minimal() +
        theme(
          axis.title = element_blank(),
          axis.text = element_blank(),
          axis.ticks = element_blank(),
          panel.grid = element_blank(),
          legend.position = "none"
        )

      if (input$show_grid) {
        zoom_level_val <- zoom_level()
        horizontal_shift_val <- horizontal_shift()
        vertical_shift_val <- vertical_shift()

        visible_x_min <- -40 * zoom_level_val + horizontal_shift_val
        visible_x_max <- 40 * zoom_level_val + horizontal_shift_val
        visible_y_min <- -40 * zoom_level_val + vertical_shift_val
        visible_y_max <- 40 * zoom_level_val + vertical_shift_val

        grid_spacing <- 10 * zoom_level_val
        grid_spacing <- max(1, min(grid_spacing, 40))

        x_start <- floor(visible_x_min / grid_spacing) * grid_spacing
        x_end <- ceiling(visible_x_max / grid_spacing) * grid_spacing
        y_start <- floor(visible_y_min / grid_spacing) * grid_spacing
        y_end <- ceiling(visible_y_max / grid_spacing) * grid_spacing

        x_grid <- seq(x_start, x_end, by = grid_spacing)
        y_grid <- seq(y_start, y_end, by = grid_spacing)

        buffer <- grid_spacing * 0.5
        x_grid <- x_grid[x_grid >= visible_x_min - buffer & x_grid <= visible_x_max + buffer]
        y_grid <- y_grid[y_grid >= visible_y_min - buffer & y_grid <= visible_y_max + buffer]

        x_breaks_func <- function(limits) {
          return(x_grid)
        }

        y_breaks_func <- function(limits) {
          return(y_grid)
        }

        label_offset <- min(grid_spacing * 0.1, 1)

        x_labels <- data.frame(
          x = x_grid,
          y = visible_y_min + label_offset,
          label = as.character(round(x_grid, 1))
        )

        y_labels <- data.frame(
          x = visible_x_min + label_offset,
          y = y_grid,
          label = as.character(round(y_grid, 1))
        )

        p <- p +
          scale_x_continuous(
            breaks = x_grid,
            expand = expansion(mult = 0)
          ) +
          scale_y_continuous(
            breaks = y_grid,
            expand = expansion(mult = 0)
          ) +
          theme(
            panel.grid.major = element_line(color = "gray80", size = 0.2),
            panel.grid.minor = element_blank(),
            axis.text = element_blank(),
            axis.ticks = element_blank(),
            axis.line = element_blank()
          ) +
          geom_text(
            data = x_labels,
            aes(x = x, y = y, label = label),
            color = "gray40",
            size = 3,
            vjust = -0.5,
            hjust = 0.5
          ) +
          geom_text(
            data = y_labels,
            aes(x = x, y = y, label = label),
            color = "gray40",
            size = 3,
            hjust = -0.5,
            vjust = 0.5
          )
      }

      element_order <- rev(layer_order())

      plot_elements <- vector("list", length(element_order))

      for (i in seq_along(element_order)) {
        element <- element_order[i]
        element_data <- values[[element]]
        plot_elements[[i]] <- switch(
          element,
          "points" = memo_draw_points(element_data, zoom_level),
          "lines" = memo_draw_lines(element_data, zoom_level),
          "annotations" = memo_draw_annotations(element_data, zoom_level),
          "loops" = memo_draw_loops(element_data, zoom_level),
          NULL
        )
      }

      p <- p + purrr::compact(plot_elements)

      return(p)
    }, error = function(e) {
      showNotification(paste("Plot error:", e$message), type = "error")
      return(ggplot() + theme_void() + ggtitle("Error: Unable to render plot"))
    })
  }

  recreate_plot_save <- function() {
    if (is.null(zoom_level()) || is.null(horizontal_shift()) ||
        is.null(vertical_shift()) || is.null(layer_order())) {
      return(ggplot() + theme_void() + ggtitle("Error: Missing required inputs"))
    }

    tryCatch({
      zoom_level <- zoom_level()
      horizontal_shift <- horizontal_shift()
      vertical_shift <- vertical_shift()
      current_layer_order <- layer_order()

      x_lim <- c(-40, 40) * zoom_level + horizontal_shift
      y_lim <- c(-40, 40) * zoom_level + vertical_shift

      p <- ggplot() +
        coord_fixed(
          ratio = 1,
          xlim = x_lim,
          ylim = y_lim,
          expand = FALSE,
          clip = "off"
        ) +
        theme_minimal() +
        theme(
          axis.title = element_blank(),
          axis.text = element_blank(),
          axis.ticks = element_blank(),
          panel.grid = element_blank(),
          legend.position = "none"
        )

      element_order <- rev(layer_order())

      plot_elements <- vector("list", length(element_order))

      for (i in seq_along(element_order)) {
        element <- element_order[i]
        element_data <- values[[element]]
        plot_elements[[i]] <- switch(
          element,
          "points" = memo_draw_points(element_data, zoom_level),
          "lines" = memo_draw_lines(element_data, zoom_level),
          "annotations" = memo_draw_annotations(element_data, zoom_level),
          "loops" = memo_draw_loops(element_data, zoom_level),
          NULL
        )
      }

      p <- p + purrr::compact(plot_elements)

      return(p)
    }, error = function(e) {
      showNotification(paste("Plot error:", e$message), type = "error")
      return(ggplot() + theme_void() + ggtitle("Error: Unable to render plot"))
    })
  }

  output$uploaded_data_preview <- DT::renderDataTable({
    req(uploaded_data())
    head(uploaded_data(), 10)
  })

  output$processing_indicator <- renderUI({
    if (is.null(uploaded_data())) {
      "No data uploaded. Using default dataset."
    } else {
      "Processing uploaded dataset..."
    }
  })


  output$data_table <- renderDT({
    if (nrow(values$points) > 0) {
      datatable(
        values$points,
        selection = "multiple",
        options = list(
          pageLength = 10,
          autoWidth = TRUE,
          dom = "ftip",
          paging = TRUE,
          stateSave = TRUE,
          rowCallback = JS(
            "function(row, data, index) {",
            "  if (data[13] == true) {",  # locked column (13th)
            "    $('td', row).css('background-color', '#f4cccc');",
            "  } else if (data[11] == false && data[12] == false && data[13] == false) {",
            "    $('td', row).css('background-color', '#d4edda');",
            "  } else {",
            "    $('td', row).css('background-color', '');",
            "  }",
            "}"
          )
        ),
        escape = FALSE,
        editable = list(
          target = "cell",
          disable = list(
            columns = c(11, 12)  # lavaan and network columns
          )
        )
      )
    } else {
      datatable(data.frame())
    }
  })


  # Render line table with clickable rows
  output$line_table <- renderDT({
    if (nrow(values$lines) > 0) {
      datatable(
        values$lines,
        selection = "multiple",
        options = list(
          pageLength = 10,
          autoWidth = TRUE,
          dom = "ftip",
          paging = TRUE,
          stateSave = TRUE,
          rowCallback = JS(
            "function(row, data, index) {",
            "  if (data[26] == true) {", # locked column (26th)
            "    $('td', row).css('background-color', '#f4cccc');", # Light red for locked rows
            "  } else if (data[23] == false && data[24] == false && data[26] == false) {", # lavaan (24th) and network (25th) are FALSE
            "    $('td', row).css('background-color', '#d4edda');", # Light green for unlocked, non-lavaan, non-network
            "  } else {",
            "    $('td', row).css('background-color', '');", # Default color for others
            "  }",
            "}"
          )
        ),
        escape = FALSE,
        editable = list(
          target = "cell",
          disable = list(
            columns = c(24, 25) # Disable editing for lavaan (24th) and network (25th) columns
          )
        )
      )
    } else {
      datatable(data.frame())
    }
  })


  output$annotation_table <- renderDT({
    if (nrow(values$annotations) > 0) {
      datatable(
        values$annotations,
        selection = "multiple",
        options = list(
          pageLength = 10,
          autoWidth = TRUE,
          dom = "ftip",
          paging = TRUE,
          stateSave = TRUE,
          rowCallback = JS(
            "function(row, data, index) {",
            "  if (data[16] == true) {", # locked column (16th)
            "    $('td', row).css('background-color', '#f4cccc');", # Light red for locked rows
            "  } else if (data[14] == false && data[15] == false && data[16] == false) {", # lavaan (14th) and network (15th) are FALSE
            "    $('td', row).css('background-color', '#d4edda');", # Light green for unlocked, non-lavaan, non-network
            "  } else {",
            "    $('td', row).css('background-color', '');", # Default color for others
            "  }",
            "}"
          )
        ),
        escape = FALSE,
        editable = list(
          target = "cell",
          disable = list(
            columns = c(14, 15) # Disable editing for lavaan (14th) and network (15th) columns
          )
        )
      )
    } else {
      datatable(data.frame())
    }
  })



  output$loop_table <- renderDT({
    if (nrow(values$loops) > 0) {
      datatable(values$loops,
                selection = "multiple",
                options = list(
                  pageLength = 10, autoWidth = TRUE,
                  dom = "ftip",
                  paging = TRUE,
                  stateSave = TRUE,
                  rowCallback = JS(
                    "function(row, data, index) {",
                    "  if (data[15] == true) {", #  'locked' column in the table
                    "    $('td', row).css('background-color', '#f4cccc');", # Light red for locked rows
                    "  } else {",
                    "    $('td', row).css('background-color', '');", # Reset color for unlocked rows
                    "  }",
                    "}"
                  )
                ),
                escape = FALSE, editable = TRUE
      )
    } else {
      datatable(data.frame())
    }
  })

  observeEvent(input$data_table_cell_edit, {
    tryCatch({
      info <- input$data_table_cell_edit
      save_state()

      # Check the column being edited and update accordingly
      if (info$col %in% c("x", "y", "size", "border_width", "alpha", "width_height_ratio", "orientation")) {
        # Ensure numeric values
        values$points[info$row, info$col] <- as.numeric(info$value)
      } else if (info$col %in% c("color", "border_color")) {
        # Validate hex color code
        if (grepl("^#(?:[0-9a-fA-F]{3}){1,2}$", info$value)) {
          values$points[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification("Invalid color input. Update skipped.", type = "error")
          return() # Skip invalid color updates
        }
      } else if (info$col == "shape") {
        # Safeguard for valid shapes
        valid_shapes <- c("circle", "square", "oval", "triangle", "rectangle", "diamond")
        if (info$value %in% valid_shapes) {
          values$points[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification(
            paste("Invalid shape input. Must be one of:", paste(valid_shapes, collapse = ", ")),
            type = "error"
          )
          return()
        }
      } else if (info$col %in% c("lavaan", "network", "locked")) {
        # Convert logical inputs to uppercase and validate
        logical_value <- toupper(info$value)
        if (logical_value %in% c("TRUE", "FALSE")) {
          values$points[info$row, info$col] <- as.logical(logical_value)
        } else {
          showNotification("Invalid logical input. Must be TRUE or FALSE.", type = "error")
          return() # Skip invalid logical updates
        }
      } else {
        # Default case for other string values
        values$points[info$row, info$col] <- info$value
      }

      # Re-render the plot with updated data

      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error processing table cell edit:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  get_current_page <- function(proxy, session) {
    as.numeric(session$clientData[[paste0("output_", proxy$id, "_state")]]$page) + 1
  }

  observeEvent(input$line_table_cell_edit, {
    tryCatch({
      info <- input$line_table_cell_edit
      save_state()

      if (info$col %in% c("x_start", "y_start", "x_end", "y_end", "width", "alpha", "ctrl_x", "ctrl_y", "ctrl_x2", "ctrl_y2", "curvature_magnitude", "curvature_asymmetry", "gradient_position", "arrow_size")) {
        # Ensure numeric values
        values$lines[info$row, info$col] <- as.numeric(info$value)
      } else if (info$col == "line_style") {
        # Validate line styles
        valid_line_styles <- c("solid", "dashed", "dotted")
        if (info$value %in% valid_line_styles) {
          values$lines[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification(
            paste("Invalid line style input. Must be one of:", paste(valid_line_styles, collapse = ", ")),
            type = "error"
          )
          return()
        }
      } else if (info$col %in% c("color", "end_color")) {
        # Validate hex color code
        if (grepl("^#(?:[0-9a-fA-F]{3}){1,2}$", info$value)) {
          values$lines[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification("Invalid color input. Update skipped.", type = "error")
          return()
        }
      } else if (info$col == "arrow_type") {
        # Validate arrow types
        valid_arrow_types <- c("open", "closed")
        if (info$value %in% valid_arrow_types) {
          values$lines[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification(
            paste("Invalid arrow type input. Must be one of:", paste(valid_arrow_types, collapse = ", ")),
            type = "error"
          )
          return()
        }
      } else if (info$col %in% c("arrow", "two_way", "lavaan", "network", "locked")) {
        # Validate logical inputs
        logical_value <- toupper(info$value)
        if (logical_value %in% c("TRUE", "FALSE")) {
          values$lines[info$row, info$col] <- as.logical(logical_value)
        } else {
          showNotification("Invalid logical input. Must be TRUE or FALSE.", type = "error")
          return()
        }
      } else {
        # Handle other cases
        values$lines[info$row, info$col] <- info$value
      }

      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error processing table cell edit:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observeEvent(input$annotation_table_cell_edit, {
    tryCatch({
      info <- input$annotation_table_cell_edit
      save_state()

      if (info$col %in% c("x", "y", "size", "angle", "alpha")) {
        # Ensure numeric values
        values$annotations[info$row, info$col] <- as.numeric(info$value)
      } else if (info$col %in% c("color", "fill")) {
        # Validate hex color code
        if (grepl("^#(?:[0-9a-fA-F]{3}){1,2}$", info$value)) {
          values$annotations[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification("Invalid color input. Update skipped.", type = "error")
          return() # Skip invalid color updates
        }
      } else if (info$col == "fontface") {
        # Validate fontface input
        valid_fontfaces <- c("plain", "bold", "italic")
        if (info$value %in% valid_fontfaces) {
          values$annotations[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification(
            paste("Invalid fontface input. Must be one of:", paste(valid_fontfaces, collapse = ", ")),
            type = "error"
          )
          return()
        }
      } else if (info$col %in% c("math_expression", "lavaan", "network", "locked")) {
        # Validate logical inputs
        logical_value <- toupper(info$value)
        if (logical_value %in% c("TRUE", "FALSE")) {
          values$annotations[info$row, info$col] <- as.logical(logical_value)
        } else {
          showNotification("Invalid logical input. Must be TRUE or FALSE.", type = "error")
          return()
        }
      } else {
        # Default case for other string values
        values$annotations[info$row, info$col] <- info$value
      }

      # Re-render the plot with updated data
      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error processing annotation table cell edit:", e$message),
        type = "error",
        duration = 5
      )
    })
  })


  observeEvent(input$loop_table_cell_edit, {
    tryCatch({
      info <- input$loop_table_cell_edit
      save_state()

      if (info$col %in% c("x_center", "y_center", "radius", "width", "alpha", "gap_size", "loop_width", "loop_height", "orientation", "arrow_size")) {
        # Ensure numeric values
        values$loops[info$row, info$col] <- as.numeric(info$value)
      } else if (info$col == "color") {
        # Validate hex color code
        if (grepl("^#(?:[0-9a-fA-F]{3}){1,2}$", info$value)) {
          values$loops[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification("Invalid color input. Update skipped.", type = "error")
          return()
        }
      } else if (info$col == "arrow_type") {
        # Validate arrow type input
        valid_arrow_types <- c("open", "closed")
        if (info$value %in% valid_arrow_types) {
          values$loops[info$row, info$col] <- as.character(info$value)
        } else {
          showNotification(
            paste("Invalid arrow type input. Must be one of:", paste(valid_arrow_types, collapse = ", ")),
            type = "error"
          )
          return()
        }
      } else if (info$col %in% c("two_way", "locked", "lavaan")) {
        # Validate logical inputs
        logical_value <- toupper(info$value)
        if (logical_value %in% c("TRUE", "FALSE")) {
          values$loops[info$row, info$col] <- as.logical(logical_value)
        } else {
          showNotification("Invalid logical input. Must be TRUE or FALSE.", type = "error")
          return() # Skip invalid logical updates
        }
      } else {
        # Default case for other string values
        values$loops[info$row, info$col] <- info$value
      }

      # Re-render the plot with updated data
      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error processing loop table cell edit:", e$message),
        type = "error",
        duration = 5
      )
    })
  })

  output$download_plot <- downloadHandler(
    filename = function() {
      paste("ggsem_figure-", Sys.Date(), ".", tolower(input$export_format), sep = "")
    },
    content = function(file) {

      x_range <- if (input$use_x_range && !is.na(input$x_range_min) && !is.na(input$x_range_max)) {
        c(input$x_range_min, input$x_range_max)
      } else {
        NULL
      }

      y_range <- if (input$use_y_range && !is.na(input$y_range_min) && !is.na(input$y_range_max)) {
        c(input$y_range_min, input$y_range_max)
      } else {
        NULL
      }

      pp <- recreate_plot_save()

      pp1 <- adjust_axis_range(pp,
                               x_range = x_range,
                               y_range = y_range,
                               fixed_aspect_ratio = input$fixed_aspect_ratio,
                               points = values$points,
                               lines = values$lines,
                               annotations = values$annotations,
                               loops = values$loops)

      zoom_value <- zoom_level()

      zoom_level <- if (!is.null(zoom_value) &&
                        length(zoom_value) > 0 &&
                        !is.na(zoom_value) &&
                        is.numeric(zoom_value)) {
        as.numeric(zoom_value)[1]
      } else {
        1.2  # Default value
      }

      save_figure(
        file,
        plot = pp1,
        zoom_level = zoom_level,
        device = switch(input$export_format,
                        "PNG" = "png",
                        "JPEG" = "jpeg",
                        "PDF" = cairo_pdf,
                        "SVG" = svglite
        )
      )
    }
  )


  output$download_selected_csv <- downloadHandler(
    filename = function() {
      paste0(tolower(input$csv_type), "-", Sys.Date(), ".csv")
    },
    content = function(file) {
      data_to_save <- switch(input$csv_type,
                             "Points CSV" = values$points,
                             "Lines CSV" = values$lines,
                             "Annotations CSV" = values$annotations,
                             "Self-loop Arrows CSV" = values$loops
      )

      write.csv(data_to_save, file, row.names = FALSE)
    }
  )


  # message about axis limits
  output$axis_info <- renderText({
    "Adjust Zoom Level / X-Level / Y-Level to change the limits of x and y axes."
  })

  # Download CSV for points
  output$download_points_csv <- downloadHandler(
    filename = function() {
      paste("points_data-", Sys.Date(), ".csv", sep = "")
    },
    content = function(file) {
      write.csv(values$points, file, row.names = FALSE)
    }
  )

  # Download CSV for lines
  output$download_lines_csv <- downloadHandler(
    filename = function() {
      paste("lines_data-", Sys.Date(), ".csv", sep = "")
    },
    content = function(file) {
      write.csv(values$lines, file, row.names = FALSE)
    }
  )

  # Download CSV for annotations
  output$download_annotations_csv <- downloadHandler(
    filename = function() {
      paste("annotations_data-", Sys.Date(), ".csv", sep = "")
    },
    content = function(file) {
      write.csv(values$annotations, file, row.names = FALSE)
    }
  )

  # Download CSV for self-loop arrows
  output$download_loops_csv <- downloadHandler(
    filename = function() {
      paste("loops_data-", Sys.Date(), ".csv", sep = "")
    },
    content = function(file) {
      write.csv(values$loops, file, row.names = FALSE)
    }
  )

  observeEvent(input$points_file, {
    tryCatch({
      req(input$points_file)
      points_data <- read.csv(input$points_file$datapath)
      points_data$lavaan <- FALSE
      points_data$network <- FALSE

      # Validate required columns for points
      required_columns <- c("x", "y", "shape", "color", "size", "orientation", "width_height_ratio")
      if (!all(required_columns %in% colnames(points_data))) {
        stop("Points file is missing required columns: ", paste(setdiff(required_columns, colnames(points_data)), collapse = ", "))
      }

      values$points <- points_data
      showNotification("Points file loaded successfully.", type = "message")


      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error loading points file:", e$message),
        type = "error"
      )
    })
  })

  observeEvent(input$lines_file, {
    tryCatch({
      req(input$lines_file)
      lines_data <- read.csv(input$lines_file$datapath)
      lines_data$lavaan <- FALSE
      lines_data$network <- FALSE

      # Validate required columns for lines
      required_columns <- c("x_start", "y_start", "x_end", "y_end", "type", "color")
      if (!all(required_columns %in% colnames(lines_data))) {
        stop("Lines file is missing required columns: ", paste(setdiff(required_columns, colnames(lines_data)), collapse = ", "))
      }

      values$lines <- lines_data
      showNotification("Lines file loaded successfully.", type = "message")

      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error loading lines file:", e$message),
        type = "error"
      )
    })
  })
  observeEvent(input$annotations_file, {
    tryCatch({
      req(input$annotations_file)
      annotations_data <- read.csv(input$annotations_file$datapath)
      annotations_data$lavaan <- FALSE
      annotations_data$network <- FALSE


      # Validate required columns for annotations
      required_columns <- c("text", "x", "y", "color", "size")
      if (!all(required_columns %in% colnames(annotations_data))) {
        stop("Annotations file is missing required columns: ", paste(setdiff(required_columns, colnames(annotations_data)), collapse = ", "))
      }

      values$annotations <- annotations_data
      showNotification("Annotations file loaded successfully.", type = "message")


      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error loading annotations file:", e$message),
        type = "error"
      )
    })
  })

  observeEvent(input$self_loop_file, {
    tryCatch({
      req(input$self_loop_file)
      self_loop_data <- read.csv(input$self_loop_file$datapath)

      # Validate required columns for loops
      required_columns <- c("x_center", "y_center", "radius", "color", "width")
      if (!all(required_columns %in% colnames(self_loop_data))) {
        stop("Self-loop file is missing required columns: ", paste(setdiff(required_columns, colnames(self_loop_data)), collapse = ", "))
      }

      values$loops <- self_loop_data
      showNotification("Self-loop file loaded successfully.", type = "message")

      output$plot <- renderPlot({
        recreate_plot()
      })
    }, error = function(e) {
      showNotification(
        paste("Error loading self-loop file:", e$message),
        type = "error"
      )
    })
  })

  ai_generated_syntax <- reactiveVal("")

  observeEvent(input$generate_lavaan_syntax, {
    req(sem_file_data())

    showModal(modalDialog(
      title = paste("Generating with", input$ai_model, "..."),
      "Please wait while AI generates lavaan syntax",
      footer = NULL,
      easyClose = FALSE
    ))

    tryCatch({

      if (input$ai_model %in% c("gemini", "openai", "mistral", "claude")) {
        api_key <- switch(
          input$ai_model,
          "gemini" = input$gemini_api_key,
          "openai" = input$openai_api_key,
          "mistral" = input$mistral_api_key,
          "claude" = input$claude_api_key
        )
        validate_api_key(api_key, input$ai_model)
      }

      ai_settings <- list(
        gemini = input$gemini_api_key,
        openai = input$openai_api_key,
        mistral = input$mistral_api_key,
        claude = input$claude_api_key,
        ollama_model = input$ollama_model
      )

      prompt_ai <- create_lavaan_prompt(model_state$data,
                                        model_type = input$model_type,
                                        max_factors = input$max_factors,
                                        comments = input$additional_comments)

      lavaan_syntax <- call_selected_ai(prompt_ai, input$ai_model, ai_settings)
      ai_generated_syntax(lavaan_syntax)

      sem_code <- generate_sem_code(input$model_type, lavaan_syntax)

      updateTextAreaInput(
        session = session,
        inputId = "lavaan_syntax",
        value = lavaan_syntax
      )

      updateTextAreaInput(
        session = session,
        inputId = "sem_code",
        value = sem_code
      )

      removeModal()
      showNotification("Lavaan syntax generated successfully! Check it in the menu below!", type = "message")

    }, error = function(e) {
      removeModal()
      showNotification(paste("Error generating syntax:", e$message), type = "error")
    })
  })

  observeEvent(input$model_type, {
    req(input$lavaan_syntax)

    sem_code <- generate_sem_code(input$model_type, input$lavaan_syntax)

    updateTextAreaInput(
      session = session,
      inputId = "sem_code",
      value = sem_code
    )
  })

  observeEvent(input$generate_lavaan_syntax_from_language, {
    req(input$natural_language_input)

    if (trimws(input$natural_language_input) == "") {
      showNotification("Please enter a natural language description of your model", type = "warning")
      return()
    }

    showModal(modalDialog(
      title = paste("Converting natural language to lavaan syntax using", input$ai_model, "..."),
      "Please wait while AI processes your request",
      footer = NULL,
      easyClose = FALSE
    ))

    tryCatch({
      if (input$ai_model %in% c("gemini", "openai", "mistral", "claude")) {
        api_key <- switch(
          input$ai_model,
          "gemini" = input$gemini_api_key,
          "openai" = input$openai_api_key,
          "mistral" = input$mistral_api_key,
          "claude" = input$claude_api_key
        )
        validate_api_key(api_key, input$ai_model)
      }

      ai_settings <- list(
        gemini = input$gemini_api_key,
        openai = input$openai_api_key,
        mistral = input$mistral_api_key,
        claude = input$claude_api_key,
        ollama_model = input$ollama_model
      )

      prompt <- create_natural_language_prompt(
        user_input = input$natural_language_input,
        data = model_state$data,
        model_type = input$model_type,
        existing_variables = names(model_state$data)
      )

      lavaan_syntax <- call_selected_ai(prompt, input$ai_model, ai_settings)

      lavaan_syntax <- clean_ai_lavaan_response(lavaan_syntax)

      updateTextAreaInput(
        session = session,
        inputId = "lavaan_syntax",
        value = lavaan_syntax
      )

      sem_code <- generate_sem_code(input$model_type, lavaan_syntax)
      updateTextAreaInput(
        session = session,
        inputId = "sem_code",
        value = sem_code
      )

      removeModal()
      showNotification("Lavaan syntax generated from natural language!", type = "message")

    }, error = function(e) {
      removeModal()
      showNotification(paste("Error generating syntax:", e$message), type = "error")
    })
  })

  observeEvent(input$capture_workflow, {
    tryCatch({
      workflow <- capture_complete_workflow(values)

      options(ggsem_last_workflow = workflow)

      values_group <- unique(c(
        extract_groups_from_data(workflow$visual_elements$points),
        extract_groups_from_data(workflow$visual_elements$lines),
        extract_groups_from_data(workflow$visual_elements$annotations),
        extract_groups_from_data(workflow$visual_elements$loops)
      ))

      undo_steps <- length(values$undo_stack)

      summary_text <- paste(
        "Workflow captured at", format(Sys.time(), "%H:%M:%S"), '\n',
        "| SEM groups:", length(workflow$sem_groups), '\n',
        "| Network groups:", length(workflow$network_groups), '\n',
        "| Total groups:", length(values_group), '\n',
        "| Previous steps:", undo_steps, '\n'
      )
      workflow_status(summary_text)

      showNotification(
        paste("Complete workflow captured!",
              length(values_group),
              "groups,",
              undo_steps, "undo steps"),
        type = "message",
        duration = 5
      )

    }, error = function(e) {
      workflow_status(paste("Error:", e$message))
      showNotification("Failed to capture workflow", type = "error")
    })
  })

  output$export_workflow <- downloadHandler(
    filename = function() {
      paste0("ggsem_workflow_", format(Sys.time(), "%Y%m%d_%H%M%S"), ".rds")
    },
    content = function(file) {
      # Get user's history preference
      history_option <- if (!is.null(input$undo_history_option)) {
        as.numeric(input$undo_history_option)
      } else {
        -1
      }

      workflow <- capture_complete_workflow(values, include_history = FALSE)

      if (history_option == 0) {
        workflow$exported_history <- list(
          history_option = 0,
          history_summary = "no history"
        )
      } else if (history_option > 0) {
        keep_states <- min(history_option, length(values$undo_stack))
        if (keep_states > 0) {
          workflow$exported_history <- list(
            undo_stack = tail(values$undo_stack, keep_states),
            history_option = history_option,
            history_summary = paste("last", history_option, "states"),
            actual_saved = keep_states,
            total_available = length(values$undo_stack),
            is_compressed = FALSE  # Add flag
          )
        } else {
          workflow$exported_history <- list(
            history_option = history_option,
            history_summary = "No history (none available)"
          )
        }
      } else {
        # Complete history
        workflow$exported_history <- list(
          undo_stack = values$undo_stack,
          redo_stack = values$redo_stack,
          history_option = -1,
          history_summary = "complete history",
          is_compressed = FALSE  # Add flag
        )
      }

      saveRDS(workflow, file, compress = TRUE)
      file_info <- file.info(file)
      actual_size <- file_info$size

      if (actual_size < 1024) {
        size_text <- paste(actual_size, "bytes")
      } else if (actual_size < 1024^2) {
        size_text <- paste(round(actual_size / 1024, 1), "KB")
      } else {
        size_text <- paste(round(actual_size / (1024^2), 1), "MB")
      }

      # Show stats
      history_steps <- length(workflow$exported_history$undo_stack %||% 0)

      showNotification(
        paste0("Saved with ", workflow$exported_history$history_summary,
              " (", history_steps, " steps, ", size_text, ")"),
        type = "message",
        duration = 5
      )
    }
  )

  output$workflow_summary <- renderText({
    workflow_status()
  })


  update_group <- function(increment = 1) {
    current_val <- as.numeric(group_value())
    new_val <- current_val + increment
    group_value(as.character(new_val))
    updateTextInput(session, "which_group", value = group_value())
  }

  reset_group <- function(increment = 1) {
    current_val <- as.numeric(group_value())
    new_val <- "1"
    group_value(as.character(new_val))
    updateTextInput(session, "which_group", value = group_value())
  }

  uploaded_workflow <- reactiveVal(NULL)

  observeEvent(input$upload_workflow, {
    req(input$upload_workflow)

    tryCatch({
      # Read the uploaded RDS file
      workflow <- readRDS(input$upload_workflow$datapath)

      # Validate the workflow structure
      if (is_valid_workflow(workflow)) {
        uploaded_workflow(workflow)
        showNotification("Workflow file loaded successfully! Click 'Load Workflow' to apply.",
                         type = "message")
      } else {
        showNotification("Invalid workflow file format", type = "error")
      }

    }, error = function(e) {
      showNotification(paste("Error loading workflow:", e$message), type = "error")
    })
  })

  is_valid_workflow <- function(workflow) {
    essential_elements <- c(
      "sem_groups", "network_groups", "modifications",
      "visual_elements", "metadata"
    )

    if (!all(essential_elements %in% names(workflow))) {
      cat("Missing essential elements:",
          paste(setdiff(essential_elements, names(workflow)), collapse = ", "), "\n")
      return(FALSE)
    }

    # Visual elements
    required_visual <- c("points", "lines", "loops", "annotations")
    if (!all(required_visual %in% names(workflow$visual_elements))) {
      cat("Missing visual elements:",
          paste(setdiff(required_visual, names(workflow$visual_elements)), collapse = ", "), "\n")
      return(FALSE)
    }

    # data_files is optional
    if (!"data_files" %in% names(workflow)) {
      workflow$data_files <- list()
    }

    return(TRUE)
  }

  output$upload_status <- renderText({
    workflow <- uploaded_workflow()
    if (is.null(workflow)) {
      return("No workflow loaded. Upload a .rds file and click 'Load Workflow'.")
    } else {
      return(paste(
        "Workflow ready to load:",
        length(workflow$sem_groups), "SEM groups,",
        length(workflow$network_groups), "network groups,",
        "captured", format(workflow$metadata$timestamp, "%Y-%m-%d %H:%M")
      ))
    }
  })

  observeEvent(input$load_workflow, {
    req(uploaded_workflow())

    tryCatch({
      workflow <- uploaded_workflow()

      # Restore visual elements
      values$points <- workflow$visual_elements$points
      values$lines <- workflow$visual_elements$lines
      values$loops <- workflow$visual_elements$loops
      values$annotations <- workflow$visual_elements$annotations

      # Restore group storage
      values$group_storage$sem <- workflow$sem_groups
      values$group_storage$network <- workflow$network_groups
      values$group_storage$modifications <- workflow$modifications$sem
      values$group_storage$modifications_network <- workflow$modifications$network

      # Update any relevant UI elements
      updateSelectInput(session, "group_select",
                        choices = workflow$group_labels,
                        selected = workflow$group_labels[1])


      if (!is.null(workflow$exported_history)) {
        if (!is.null(workflow$exported_history$undo_stack)) {
          values$undo_stack <- workflow$exported_history$undo_stack %||% list()
          values$redo_stack <- workflow$exported_history$redo_stack %||% list()

          history_info <- tolower(workflow$exported_history$history_summary) %||% "unknown"
          showNotification(
            paste("Workflow loaded with", history_info,
                  " (", length(values$undo_stack), "undo steps)"),
            type = "message",
            duration = 5
          )
        } else {
          values$undo_stack <- list()
          values$redo_stack <- list()
          showNotification(
            "Workflow loaded (no undo history saved)",
            type = "message",
            duration = 3
          )
        }
      }
      # Backward compatibility with OLD compressed format
      else if (!is.null(workflow$history_state)) {
        if (!is.null(workflow$history_state$undo_stack)) {
          if (length(workflow$history_state$undo_stack) > 0 &&
              is.raw(workflow$history_state$undo_stack[[1]])) {
            decompress_stack <- function(stack) {
              lapply(stack, function(compressed_state) {
                decompressed <- memDecompress(compressed_state, type = "gzip")
                unserialize(decompressed)
              })
            }
            values$undo_stack <- decompress_stack(workflow$history_state$undo_stack %||% list())
            values$redo_stack <- decompress_stack(workflow$history_state$redo_stack %||% list())
          } else {
            # Already uncompressed
            values$undo_stack <- workflow$history_state$undo_stack %||% list()
            values$redo_stack <- workflow$history_state$redo_stack %||% list()
          }

          history_info <- tolower(workflow$metadata$history_summary) %||% "complete history"
          showNotification(
            paste("Workflow loaded with", history_info,
                  " (", length(values$undo_stack), "undo steps)"),
            type = "message",
            duration = 5
          )
        } else {
          values$undo_stack <- list()
          values$redo_stack <- list()
          showNotification(
            "Workflow loaded (no undo history saved)",
            type = "message",
            duration = 3
          )
        }
      }
      else {
        # No history at all
        values$undo_stack <- list()
        values$redo_stack <- list()
        showNotification(
          "Workflow loaded (no undo history)",
          type = "message",
          duration = 3
        )
      }

      update_group(1)

      uploaded_workflow(NULL)
      reset("upload_workflow")

      output$plot <- renderPlot({
        recreate_plot()
      })

    }, error = function(e) {
      showNotification(paste("Error loading workflow:", e$message), type = "error")
    })
  })

  create_mod_defaults <- function(type = "sem") {

    if (type == "sem") {
      list(
        node = data.frame(
          text = character(),
          color = character(),
          alpha = numeric(),
          shape = character(),
          size = numeric(),
          border_color = character(),
          border_width = numeric(),
          width_height_ratio = numeric(),
          stringsAsFactors = FALSE
        ),
        edge = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          color = character(),
          width = numeric(),
          alpha = numeric(),
          line_style = character(),
          color_type = character(),
          end_color = character(),
          gradient_position = numeric(),
          stringsAsFactors = FALSE
        ),
        edgelabel = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          color = character(),
          fill = character(),
          size = numeric(),
          alpha = numeric(),
          angle = numeric(),
          font = character(),
          fontface = character(),
          stringsAsFactors = FALSE
        ),
        edgelabel_xy = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          x_shift = numeric(),
          y_shift = numeric(),
          stringsAsFactors = FALSE
        ),
        edgelabel_text = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          text = character(),
          math_expression = logical(),
          stringsAsFactors = FALSE
        ),
        node_xy = data.frame(
          text = character(),
          x_shift = numeric(),
          y_shift = numeric(),
          stringsAsFactors = FALSE
        ),
        latent_node_xy = data.frame(
          text = character(),
          x_shift = numeric(),
          y_shift = numeric(),
          node_type = character(),
          stringsAsFactors = FALSE
        ),
        latent_node_angle = data.frame(
          text = character(),
          angle = numeric(),
          node_type = character(),
          stringsAsFactors = FALSE
        ),
        edge_xy = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          start_x_shift = numeric(),
          start_y_shift = numeric(),
          end_x_shift = numeric(),
          end_y_shift = numeric(),
          stringsAsFactors = FALSE
        ),
        cov_edge = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          curvature_magnitude = numeric(),
          rotate_curvature = logical(),
          curvature_asymmetry = numeric(),
          x_shift = numeric(),
          y_shift = numeric(),
          stringsAsFactors = FALSE
        ),
        nodelabel = data.frame(
          text = character(),
          color = character(),
          size = numeric(),
          alpha = numeric(),
          angle = numeric(),
          font = character(),
          fontface = character(),
          stringsAsFactors = FALSE
        ),
        nodelabel_xy = data.frame(
          text = character(),
          x_shift = numeric(),
          y_shift = numeric(),
          stringsAsFactors = FALSE
        ),
        nodelabel_text = data.frame(
          text = character(),
          nodelabel = character(),
          math_expression = logical(),
          stringsAsFactors = FALSE
        ),
        loop = data.frame(
          text = character(),
          color = character(),
          alpha = numeric(),
          radius = numeric(),
          width = numeric(),
          type = character(),
          arrow_size = numeric(),
          gap_size = numeric(),
          two_way = logical(),
          stringsAsFactors = FALSE
        ),
        loop_xy = data.frame(
          text = character(),
          x_shift = numeric(),
          y_shift = numeric(),
          stringsAsFactors = FALSE
        ),
        loop_location = data.frame(
          text = character(),
          loop_location = numeric(),
          stringsAsFactors = FALSE
        ),
        looplabel = data.frame(
          text = character(),
          color = character(),
          size = numeric(),
          alpha = numeric(),
          angle = numeric(),
          font = character(),
          fontface = character(),
          stringsAsFactors = FALSE
        ),
        looplabel_text = data.frame(
          text = character(),
          looplabel = character(),
          math_expression = logical(),
          stringsAsFactors = FALSE
        )
      )
    } else {
      list(
        node = data.frame(
          text = character(),
          color = character(),
          alpha = numeric(),
          shape = character(),
          size = numeric(),
          border_color = character(),
          border_width = numeric(),
          width_height_ratio = numeric(),
          stringsAsFactors = FALSE
        ),
        edge = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          color = character(),
          width = numeric(),
          alpha = numeric(),
          line_style = character(),
          color_type = character(),
          end_color = character(),
          gradient_position = numeric(),
          stringsAsFactors = FALSE
        ),
        nodelabel = data.frame(
          text = character(),
          color = character(),
          size = numeric(),
          alpha = numeric(),
          angle = numeric(),
          font = character(),
          fontface = character(),
          stringsAsFactors = FALSE
        ),
        edgelabel = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          color = character(),
          fill = character(),
          size = numeric(),
          alpha = numeric(),
          angle = numeric(),
          font = character(),
          fontface = character(),
          stringsAsFactors = FALSE
        ),
        edgelabel_xy = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          x_shift = numeric(),
          y_shift = numeric(),
          stringsAsFactors = FALSE
        ),
        edgelabel_text = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          text = character(),
          math_expression = logical(),
          stringsAsFactors = FALSE
        ),
        nodelabel_text = data.frame(
          text = character(),
          nodelabel = character(),
          math_expression = logical(),
          stringsAsFactors = FALSE
        ),
        edge_curvature = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          curvature_magnitude = numeric(),
          rotate_curvature = logical(),
          curvature_asymmetry = numeric(),
          x_shift = numeric(),
          y_shift = numeric(),
          stringsAsFactors = FALSE
        ),
        node_xy = data.frame(
          text = character(),
          x_shift = numeric(),
          y_shift = numeric(),
          stringsAsFactors = FALSE
        ),
        edge_xy = data.frame(
          lhs = character(),
          op = character(),
          rhs = character(),
          start_x_shift = numeric(),
          start_y_shift = numeric(),
          end_x_shift = numeric(),
          end_y_shift = numeric(),
          stringsAsFactors = FALSE
        )
      )
    }
  }

  create_group_storage <- function(type = "sem") {

    if (type == "sem") {
      list(
        nodes = NULL,
        edges = NULL,
        intercepts = FALSE,
        current = NULL,
        original = NULL,
        data = NULL,
        data_file = NULL,
        node_coords0 = NULL,
        last_sem_paths = NULL,
        # bundle
        bundleObject = NULL,
        bundleModelObject = NULL,
        bundleLavaanString = NULL,
        # Etc
        last_lavaan_syntax = NULL,
        last_sem_code = NULL,
        last_lavaan_layout = NULL,
        last_lavaan_layout_matrix = NULL,
        last_lavaan_layout_matrix0 = NULL,
        last_custom_sem = NULL,
        last_flip_sem_layout = NULL,
        last_flip_sem_layout_direction = NULL,
        last_rotate_sem_layout = NULL,
        last_rotate_sem_layout_angle = NULL,
        last_relative_x_position = NULL,
        last_relative_y_position = NULL,
        last_center_x_position = NULL,
        last_center_y_position = NULL,
        last_lavaan_rotate_curvature = NULL,
        last_lavaan_curvature_magnitude = NULL,
        last_lavaan_curvature_asymmetry = NULL,
        last_lavaan_curved_x_shift = NULL,
        last_lavaan_curved_y_shift = NULL,
        last_lavaan_arrow_type = NULL,
        last_lavaan_arrow_size = NULL,
        last_lavaan_arrow_location = NULL,
        last_annotate_sem_est = NULL,
        last_sig_label_fontface = NULL,
        last_non_sig_label_fontface = NULL,
        last_sig_path_color = NULL,
        last_non_sig_path_color = NULL,
        last_std_est = FALSE,
        last_ustd_est = TRUE,
        last_conf_int = FALSE,
        last_p_val = FALSE,
        last_highlight_free_path = NULL,
        last_free_path_color = NULL,
        past_fixed_path_color = NULL,
        last_free_label_fontface = NULL,
        last_fixed_label_fontface = NULL,
        last_ff_params_edge = NULL,
        last_ff_params_loop = NULL,
        last_ff_params_edgelabel = NULL,
        last_ff_params_looplabel = NULL,
        last_highlight_sig_path = NULL,
        last_p_val_alpha = NULL,
        last_multigroup_data_upload = FALSE,
        last_group_var = NULL,
        last_group_level = NULL,
        last_invariance_level = NULL,
        last_highlight_multi_group = NULL,
        last_highlight_multi_group_edges = FALSE,
        last_highlight_multi_group_edgelabels = FALSE,
        last_highlight_multi_group_loops = FALSE,
        last_highlight_multi_group_looplabels = FALSE,
        last_p_val_alpha_compare = NULL,
        last_bayes_significance = NULL,
        last_min_rope = NULL,
        last_max_rope = NULL,
        # Multi-group free/fixed
        last_highlight_free_path_multi_group = NULL,
        last_highlight_free_path_multi_group_invariance = NULL,
        last_highlight_free_path_multi_group_difference = NULL,
        last_invariance_color = NULL,
        last_invariance_line_width = NULL,
        last_invariance_fontface = NULL,
        last_group_diff_color = NULL,
        last_group_diff_line_width = NULL,
        last_group_diff_fontface = NULL,
        last_ff_params_edge_multi = NULL,
        last_ff_params_edgelabel_multi = NULL,
        last_ff_params_loop_multi = NULL,
        last_ff_params_looplabel_multi = NULL,
        ## Multi-group highlight sig paths
        last_sig_diff_edge = NULL,
        last_sig_diff_edgelabel = NULL,
        last_sig_diff_path_color = NULL,
        last_sig_diff_path_line_width = NULL,
        last_sig_diff_edgelabel_fontface = NULL,
        last_sig_diff_loop = NULL,
        last_sig_diff_looplabel = NULL,
        last_p_val_alpha_compare = NULL,
        # Nodes shapes and sizes
        last_latent_shape = NULL,
        last_observed_shape = NULL,
        last_int_shape = NULL,
        last_point_size_latent = NULL,
        last_point_size_observed = NULL,
        last_point_size_int = NULL,
        last_width_height_ratio_latent = NULL,
        last_width_height_ratio_observed = NULL,
        last_width_height_ratio_int = NULL,
        # Line properties
        last_line_width = NULL,
        last_line_alpha = NULL,
        last_line_endpoint_spacing = NULL,
        last_edge_color = NULL,
        last_lavaan_arrow_location = NULL,
        # Text properties - Latent
        last_text_size_latent = NULL,
        last_text_font_latent = NULL,
        last_text_color_latent = NULL,
        last_text_alpha_latent = NULL,
        last_text_fontface_latent = NULL,
        # Text properties - Others (Observed/Intercept)
        last_text_size_others = NULL,
        last_text_font_others = NULL,
        last_text_color_others = NULL,
        last_text_alpha_others = NULL,
        last_text_fontface_others = NULL,
        # Text properties - Edges
        last_text_size_edges = NULL,
        last_text_font_edges = NULL,
        last_text_color_edges = NULL,
        last_text_color_fill = NULL,
        last_apply_text_color_fill = NULL,
        last_text_alpha_edges = NULL,
        last_text_fontface_edges = NULL,
        # Remove edgelabels =
        last_remove_edgelabels = NULL,
        # Point colors
        last_point_color_latent = NULL,
        last_point_color_observed = NULL,
        last_point_color_int = NULL,
        # Node border properties
        last_node_border_color = NULL,
        last_node_border_width = NULL,
        # General arrow properties
        last_arrow_type = NULL,
        last_arrow_size = NULL,
        # Parameter-specific modifications
        last_modify_params_node = FALSE,
        last_param_node_select = NULL,
        last_modify_params_node_color = NULL,
        last_modify_params_node_alpha = NULL,
        last_modify_params_node_shape = NULL,
        last_modify_params_node_size = NULL,
        last_modify_params_node_border_color = NULL,
        last_modify_params_node_border_width = NULL,
        last_modify_params_node_width_height_ratio = NULL,
        last_modify_params_edge = FALSE,
        last_param_edge_select = NULL,
        last_modify_params_edge_color = NULL,
        last_modify_params_edge_linewidth = NULL,
        last_modify_params_edge_alpha = NULL,
        last_modify_params_edge_line_style = NULL,
        last_modify_params_edge_color_type = NULL,
        last_modify_params_edge_gradient_position = NULL,
        last_modify_params_edge_end_color = NULL,
        last_modify_params_edgelabel = FALSE,
        last_param_edgelabel_select = NULL,
        last_modify_params_edgelabel_color = NULL,
        last_apply_modify_params_edgelabel_fill = NULL,
        last_modify_params_edgelabel_fill = NULL,
        last_modify_params_edgelabel_size = NULL,
        last_modify_params_edgelabel_alpha = NULL,
        last_modify_params_edgelabel_angle = NULL,
        last_modify_params_edgelabel_font = NULL,
        last_modify_params_edgelabel_fontface = NULL,
        last_modify_params_nodelabel = FALSE,
        last_modify_params_nodelabel_color = NULL,
        last_modify_params_nodelabel_size = NULL,
        last_modify_params_nodelabel_alpha = NULL,
        last_modify_params_nodelabel_angle = NULL,
        last_modify_params_nodelabel_font = NULL,
        last_modify_params_nodelabel_fontface = NULL,
        last_modify_params_nodelabel_text = FALSE,
        last_modify_params_nodelabel_select = NULL,
        last_params_nodelabel_text = NULL,
        last_params_nodelabel_nodelabel = NULL,
        last_params_nodelabel_text_math_expression = FALSE,
        last_modify_params_node_xy = FALSE,
        last_modify_params_node_shift_x = NULL,
        last_modify_params_node_shift_y = NULL,
        last_modify_which_node_shift_xy = NULL,
        last_modify_params_latent_node_xy = FALSE,
        last_modify_params_latent_node_shift_x = NULL,
        last_modify_params_latent_node_shift_y = NULL,
        last_modify_which_latent_node_shift_xy = NULL,
        last_modify_params_latent_node_angle = FALSE,
        last_modify_params_latent_node_angle_value = NULL,
        last_modify_which_latent_node_angle = NULL,
        last_modify_params_cov_edge = FALSE,
        last_modify_which_cov_edge = NULL,
        last_modify_params_cov_edge_curvature_magnitude = NULL,
        last_modify_params_cov_edge_rotate_curvature = NULL,
        last_modify_params_cov_edge_curvature_asymmetry = NULL,
        last_modify_params_cov_edge_x_shift = NULL,
        last_modify_params_cov_edge_y_shift = NULL,
        last_modify_params_edgelabel_xy = FALSE,
        last_modify_params_edgelabel_xy_select = NULL,
        last_modify_params_edgelabel_shift_x = NULL,
        last_modify_params_edgelabel_shift_y = NULL,
        last_modify_params_nodelabel_xy = FALSE,
        last_modify_params_nodelabel_xy_select = NULL,
        last_modify_params_nodelabel_shift_x = NULL,
        last_modify_params_nodelabel_shift_y = NULL,
        last_modify_params_edge_xy = FALSE,
        last_modify_params_edge_xy_select = NULL,
        last_modify_params_edge_x_start = NULL,
        last_modify_params_edge_x_end = NULL,
        last_modify_params_edge_y_start = NULL,
        last_modify_params_edge_y_end = NULL,
        last_apply_global_nodes = NULL,
        last_apply_global_edges = NULL,
        last_apply_global_annotations = NULL,
        # combined
        multi_group_sem_combine_menu = FALSE,
        first_group = NULL,
        first_group_id = NULL,
        first_model = NULL,
        first_object = NULL,
        second_group = NULL,
        second_group_id = NULL,
        second_object = NULL,
        second_model = NULL,
        last_sep_by = NULL,
        multi_combine_real = FALSE,
        # loop
        last_residuals = FALSE,
        last_lavaan_radius = NULL,
        last_lavaan_width_loop = NULL,
        last_lavaan_line_color_loop = NULL,
        last_lavaan_line_alpha_loop = NULL,
        last_lavaan_arrow_type_loop = NULL,
        last_lavaan_arrow_size_loop = NULL,
        last_lavaan_gap_size_loop = NULL,
        last_lavaan_height_loop = NULL,
        last_lavaan_two_way_arrow_loop = TRUE,
        last_lavaan_loop_offset = NULL,
        last_residuals_orientation_type = NULL,
        last_modify_params_bezier_network_edges = FALSE,
        last_loop_names_remove_ui = NULL,
        last_loop_names_remove_hi = NULL,
        last_modify_params_loop = FALSE,
        last_param_loop_select = NULL,
        last_modify_params_loop_color = NULL,
        last_modify_params_loop_alpha = NULL,
        last_modify_params_loop_radius = NULL,
        last_modify_params_loop_width = NULL,
        last_modify_params_loop_type = NULL,
        last_modify_params_loop_arrow_size = NULL,
        last_modify_params_loop_gap_size = NULL,
        last_modify_params_loop_two_way = NULL,
        last_modify_params_loop_xy = FALSE,
        last_modify_params_loop_shift_x = NULL,
        last_modify_params_loop_shift_y = NULL,
        last_modify_which_loop_shift_xy = NULL,
        last_modify_params_loop_location = FALSE,
        last_modify_params_loop_location_value = NULL,
        last_modify_which_loop_location = NULL,
        last_modify_params_looplabel = FALSE,
        last_param_looplabel_select = NULL,
        last_modify_params_looplabel_color = NULL,
        last_modify_params_looplabel_size = NULL,
        last_modify_params_looplabel_alpha = NULL,
        last_modify_params_looplabel_angle = NULL,
        last_modify_params_looplabel_font = NULL,
        last_modify_params_looplabel_fontface = NULL,
        last_modify_params_looplabel_fill = NULL,
        last_apply_modify_params_looplabel_fill = FALSE,
        last_modify_params_looplabel_xy = FALSE,
        last_modify_params_looplabel_shift_x = NULL,
        last_modify_params_looplabel_shift_y = NULL,
        last_modify_which_looplabel_shift_xy = NULL,
        last_modify_params_looplabel_text = FALSE,
        last_params_looplabel_text = NULL,
        last_params_looplabel_looplabel = NULL,
        last_params_looplabel_text_math_expression = FALSE
      )
    } else if (type == "network") {
      list(
        edges = NULL,
        nodes = NULL,
        intercepts = FALSE,
        weights = NULL,
        layout = NULL,
        data = NULL,
        # bundle
        bundleObject = NULL, #  previously network_state$data
        bundleModelObject = NULL,
        # Etc
        last_layout_method = NULL,
        last_dim_reduction_method = NULL,
        last_is_directed = NULL,
        last_layout_x = 30,
        last_layout_y = 30,
        last_x_center = 0,
        last_y_center = 0,
        last_flip_network_layout = NULL,
        last_flip_network_layout_direction = NULL,
        last_rotate_network_layout = NULL,
        last_rotate_network_layout_angle = NULL,
        last_node_shape = NULL,
        last_node_size = NULL,
        last_node_alpha = NULL,
        last_node_fill_color = NULL,
        last_node_border_color = NULL,
        last_node_border_width = NULL,
        last_node_width_height_ratio = NULL,
        last_line_width = NULL,
        last_line_color = NULL,
        last_line_alpha = NULL,
        last_min_edge_width = NULL,
        last_max_edge_width = NULL,
        last_scale_edge_width = NULL,
        last_line_endpoint_spacing = NULL,
        last_arrow_type = NULL,
        last_arrow_size = NULL,
        last_node_label_font = NULL,
        last_node_label_size = NULL,
        last_node_label_color = NULL,
        last_node_label_alpha = NULL,
        last_node_label_fontface = NULL,
        last_edge_label_font = NULL,
        last_edge_label_size = NULL,
        last_edge_label_color = NULL,
        last_apply_edge_label_fill = NULL,
        last_edge_label_fill = NULL,
        last_edge_label_alpha = NULL,
        last_edge_label_fontface = NULL,
        last_random_seed = NULL,
        last_remove_edgelabels_net = NULL,
        last_use_clustering = NULL,
        last_clustering_method = NULL,
        last_cluster_palette = NULL,
        last_apply_bipartite_nodes = NULL,
        last_apply_bipartite_edges = NULL,
        last_apply_bipartite_annotations = NULL,
        last_modify_params_edge_xy_network = FALSE,
        last_modify_params_nodelabel_text_network = FALSE,
        last_params_nodelabel_text_network_math_expression = FALSE,
        last_modify_params_nodelabel_xy_network = FALSE,
        last_modify_params_node_xy_network = FALSE,
        last_modify_params_edgelabel_xy_network = FALSE,
        last_annotate_edges = NULL,
        last_bezier_network_edges = NULL,
        # last_bezier = FALSE,
        last_network_edges_curvature_magnitude = 0.5,
        # last_bezier_rotate_curvature = FALSE,
        last_network_edges_curvature_asymmetry = 0,
        last_modify_params_edge_network = FALSE,
        last_modify_params_bezier_edge_network = FALSE,
        last_param_nodelabel_network_select = FALSE,
        last_modify_params_edgelabel_network = FALSE,
        last_modify_params_node_network = FALSE,
        last_param_nodelabel_text_network_select = FALSE,
        last_param_edge_network_select = NULL,
        last_modify_params_edge_network_color = NULL,
        last_modify_params_edge_network_linewidth = NULL,
        last_modify_params_edge_network_alpha = NULL,
        last_modify_params_edge_network_line_style = NULL,
        last_modify_params_edge_network_gradient_position = NULL,
        last_modify_params_edge_network_end_color = NULL,
        last_modify_params_edge_network_color_type = NULL,
        last_modify_param_network_edge_x_shift = NULL,
        last_modify_param_network_edge_y_shift = NULL,
        last_param_bezier_edge_network_select = NULL,
        last_modify_params_network_edges_curvature_magnitude = NULL,
        last_modify_params_network_edges_curvature_asymmetry = NULL,
        last_modify_params_network_edges_rotate_curvature = NULL,
        last_modify_params_nodelabel_network = FALSE,
        last_modify_params_nodelabel_network_color = NULL,
        last_modify_params_nodelabel_network_size = NULL,
        last_modify_params_nodelabel_network_alpha = NULL,
        last_modify_params_nodelabel_network_angle = NULL,
        last_modify_params_nodelabel_network_font = NULL,
        last_modify_params_nodelabel_network_fontface = NULL,
        last_param_edgelabel_network_select = NULL,
        last_modify_params_edgelabel_network_color = NULL,
        last_apply_modify_params_edgelabel_network_fill = NULL,
        last_modify_params_edgelabel_network_fill = NULL,
        last_modify_params_edgelabel_network_size = NULL,
        last_modify_params_edgelabel_network_alpha = NULL,
        last_modify_params_edgelabel_network_angle = NULL,
        last_modify_params_edgelabel_network_font = NULL,
        last_modify_params_edgelabel_network_fontface = NULL,
        last_param_node_network_select = NULL,
        last_modify_params_node_network_color = NULL,
        last_modify_params_node_network_alpha = NULL,
        last_modify_params_node_network_shape = NULL,
        last_modify_params_node_network_size = NULL,
        last_modify_params_node_network_border_color = NULL,
        last_modify_params_node_network_border_width = NULL,
        last_modify_params_node_network_width_height_ratio = NULL,
        last_params_nodelabel_network_nodelabel = NULL,
        last_param_edge_xy_network_select = NULL,
        last_param_node_xy_network_select = NULL,
        last_param_edgelabel_xy_network_select = NULL,
        last_param_nodelabel_xy_network_select = NULL,
        last_network_edges_rotate_curvature = NULL,
        last_modify_params_edge_network_start_shift_x = NULL,
        last_modify_params_edge_network_start_shift_y = NULL,
        last_modify_params_edge_network_end_shift_x = NULL,
        last_modify_params_edge_network_end_shift_y = NULL,
        last_modify_param_network_edge_x_shift = NULL,
        last_modify_param_network_edge_y_shift = NULL,
        last_modify_params_nodelabel_network_shift_x = NULL,
        last_modify_params_nodelabel_network_shift_y = NULL,
        last_modify_params_edgelabel_network_shift_x = NULL,
        last_modify_params_edgelabel_network_shift_y = NULL,
        last_modify_params_edgelabel_text_network = FALSE,
        last_params_edgelabel_text_network = NULL,
        last_params_edgelabel_edgelabel_network = NULL,
        last_params_edgelabel_text_network_math_expression = FALSE,
        last_apply_global_nodes = NULL,
        last_apply_global_edges = NULL,
        last_apply_global_annotations = NULL,
        # gradient nodes
        last_node_fill_color_net_gradient = NULL,
        last_grad_start_color_net = NULL,
        last_grad_end_color_net = NULL,
        last_gradient_position_points_net = NULL
      )
    } else if (type == "group_settings") {
      list(
        # selection
        last_group_aesthetics_point_only = FALSE,
        last_group_aesthetics_line_only = FALSE,
        last_group_aesthetics_annotation_only = FALSE,
        last_group_aesthetics_loop_only = FALSE,
        # points
        last_point_color_group = NULL,
        last_shape_group = NULL,
        last_point_size_group = NULL,
        last_border_width_group = NULL,
        last_border_color_group = NULL,
        last_point_alpha_group = NULL,
        last_point_orientation_group = NULL,
        last_width_height_ratio_group = NULL,
        # lines
        last_line_color_group = NULL,
        last_color_type_group = NULL,
        last_end_color_group = NULL,
        last_gradient_position_group = NULL,
        last_line_width_group = NULL,
        last_line_alpha_group = NULL,
        last_line_type_group = NULL,
        last_color_type_group = NULL,
        last_line_type_group = NULL,
        last_curvature_magnitude_group = NULL,
        last_rotate_curvature_group = NULL,
        last_curvature_asymmetry_group = NULL,
        last_line_type_group = NULL,
        last_arrow_type_group = NULL,
        last_arrow_size_group = NULL,
        last_two_way_arrow_group = NULL,
        # annotations
        last_text_font_group = NULL,
        last_text_size_group = NULL,
        last_text_color_group = NULL,
        last_text_angle_group = NULL,
        last_text_alpha_group = NULL,
        last_text_fontface_group = NULL,
        last_apply_text_fill_group = NULL,
        last_text_fill_group = NULL,
        # loops
        last_radius_group = NULL,
        last_line_width_loop_group = NULL,
        last_line_color_loop_group = NULL,
        last_line_alpha_loop_group = NULL,
        last_arrow_type_loop_group = NULL,
        last_arrow_size_loop_group = NULL,
        last_width_loop_group = NULL,
        last_height_loop_group = NULL,
        last_gap_size_loop_group = NULL,
        last_orientation_loop_group = NULL,
        last_two_way_arrow_loop_group = NULL,
        # position
        last_group_shift_xy = FALSE,
        last_position_group_select = NULL,
        last_group_shift_x = NULL,
        last_group_shift_y = NULL,
        # orientation
        last_rotation_angle = NULL
      )
    }
  }

  observeEvent(input$apply_pre_palette, {
    req(input$pre_palette)

    get_text_color <- function(hex_color) {
      rgb_val <- col2rgb(hex_color)
      luminance <- (0.299 * rgb_val[1] + 0.587 * rgb_val[2] + 0.114 * rgb_val[3]) / 255
      ifelse(luminance > 0.65, "#000000", "#FFFFFF")
    }

    if (input$pre_palette == 'Earth Tones') {
      colors <- c('#CD6B5B', '#3E7671', '#887E6D')
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Vibrant Primary') {
      colors <- c('#4E79A7', '#F28E2B', '#59A14F')
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Cool Blues') {
      colors <- c('#2E5A8C', '#6BAED6', '#9ECAE1')
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Warm Contrast') {
      colors <- c('#D1495B', '#EDAE49', '#66A182')
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Jewel Tones') {
      colors <- c('#6A4C93', '#1982C4', '#8AC926')
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Pastel Soft') {
      colors <- c('#FFA5AB', '#B5EAD7', '#C7CEEA')
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Forest Green') {
      colors <- c('#2D5A27', '#6A994E', '#A7C957')
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Sunset Warm') {
      colors <- c('#FF6B35', '#F7C59F', '#2A9D8F')
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    # Additional balanced color palettes
    else if (input$pre_palette == 'Royal Contrast') {
      colors <- c('#2C3E50', '#E74C3C', '#F39C12')  # Dark blue, Red, Orange
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Forest Canopy') {
      colors <- c('#27AE60', '#F1C40F', '#c59ad7')  # Green, Yellow, Purple
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Ocean Sunset') {
      colors <- c('#2980B9', '#E67E22', '#16A085')  # Blue, Orange, Teal
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Berry Garden') {
      colors <- c('#8E44AD', '#C0392B', '#F39C12')  # Purple, Red, Orange
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Slate & Scarlet') {
      colors <- c('#34495E', '#E74C3C', '#1ABC9C')  # Dark blue, Red, Teal
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Autumn Rich') {
      colors <- c('#D35400', '#27AE60', '#F1C40F')  # Orange, Green, Yellow
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Jewel Rich') {
      colors <- c('#16A085', '#8E44AD', '#C0392B')  # Teal, Purple, Red
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }
    else if (input$pre_palette == 'Nightfall & Ember') {
      colors <- c('#2C3E50', '#E67E22', '#27AE60')  # Dark blue, Orange, Green
      updateColourInput(session, "latent_color_input", value = colors[1])
      updateColourInput(session, "observed_color_input", value = colors[2])
      updateColourInput(session, "int_color_input", value = colors[3])
      updateColourInput(session, "text_color_latent", value = get_text_color(colors[1]))
      updateColourInput(session, "text_color_others", value = get_text_color(colors[2]))
    }

    showNotification(
      HTML(paste("Updated the color input values to <b>", input$pre_palette, "<b> color palette.")),
      type = "message"
    )

  })

}

shinyApp(ui = ui, server = server)
