# test-coverage-utils-validation-41.R
# Comprehensive tests for R/utils-validation.R
# Targets uncovered functions and branches

# Make internal functions available for testing
skip_coverage_tests()

validate_network <- cograph:::validate_network
validate_color <- cograph:::validate_color
validate_range <- cograph:::validate_range
validate_choice <- cograph:::validate_choice
validate_length <- cograph:::validate_length
recycle_to_length <- cograph:::recycle_to_length
expand_param <- cograph:::expand_param
resolve_aesthetic <- cograph:::resolve_aesthetic

# ============================================
# validate_network() - Tests for network validation
# ============================================

test_that("validate_network accepts CographNetwork R6 objects", {
  adj <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- CographNetwork$new(adj)

  result <- validate_network(net)
  expect_true(inherits(result, "CographNetwork"))
})

test_that("validate_network accepts cograph_network S3 objects", {
  adj <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- cograph(adj)

  # S3 cograph_network stores nodes/edges/meta at top level (no $network slot).
  # validate_network should return the object unchanged.
  result <- validate_network(net)
  expect_identical(result, net)
  expect_true(inherits(result, "cograph_network"))
})

test_that("validate_network rejects non-network objects", {
  expect_error(
    validate_network(data.frame(a = 1:3)),
    "must be a CographNetwork object"
  )

  expect_error(
    validate_network("not a network"),
    "must be a CographNetwork object"
  )

  expect_error(
    validate_network(list(x = 1, y = 2)),
    "must be a CographNetwork object"
  )
})

test_that("validate_network uses custom arg_name in error messages", {
  expect_error(
    validate_network(NULL, arg_name = "my_network"),
    "my_network must be a CographNetwork object"
  )

  expect_error(
    validate_network(42, arg_name = "input_net"),
    "input_net must be a CographNetwork object"
  )
})

test_that("validate_network returns cograph_network unchanged (no $network unwrap)", {
  adj <- matrix(c(0, 1, 0, 1, 0, 1, 0, 1, 0), nrow = 3)
  net <- cograph(adj)

  result <- validate_network(net)
  expect_identical(result, net)
  # Regression guard: historical code tried x$network and silently returned
  # NULL. Any future refactor that reinstates that path will fail here.
  expect_false(is.null(result))
})

# ============================================
# validate_color() - Tests for color validation
# ============================================

test_that("validate_color accepts NULL", {
  expect_true(validate_color(NULL))
})

test_that("validate_color accepts NA", {
  expect_true(validate_color(NA))
})

test_that("validate_color accepts 'transparent'", {
  expect_true(validate_color("transparent"))
})

test_that("validate_color accepts valid color names", {
  expect_true(validate_color("red"))
  expect_true(validate_color("blue"))
  expect_true(validate_color("green"))
  expect_true(validate_color("black"))
  expect_true(validate_color("white"))
  expect_true(validate_color("gray50"))
})

test_that("validate_color accepts hex colors", {
  expect_true(validate_color("#FF0000"))
  expect_true(validate_color("#00FF00"))
  expect_true(validate_color("#0000FF"))
  expect_true(validate_color("#FFFFFF"))
  expect_true(validate_color("#000000"))
  expect_true(validate_color("#FF00FF80"))  # with alpha
})

test_that("validate_color accepts RGB specification", {
  expect_true(validate_color(rgb(1, 0, 0)))
  expect_true(validate_color(rgb(0.5, 0.5, 0.5)))
})

test_that("validate_color rejects invalid colors", {
  expect_error(
    validate_color("notacolor"),
    "is not a valid color"
  )

  expect_error(
    validate_color("NOTVALID"),
    "is not a valid color"
  )
})

test_that("validate_color uses custom arg_name in error messages", {
  expect_error(
    validate_color("invalid_color", arg_name = "fill_color"),
    "fill_color is not a valid color"
  )

  expect_error(
    validate_color("xyz", arg_name = "border"),
    "border is not a valid color"
  )
})

# ============================================
# validate_range() - Tests for numeric range validation
# ============================================

test_that("validate_range accepts numeric within default range", {
  expect_true(validate_range(0))
  expect_true(validate_range(100))
  expect_true(validate_range(-100))
  expect_true(validate_range(c(1, 2, 3)))
})

test_that("validate_range accepts numeric within specified min", {
  expect_true(validate_range(5, min = 0))
  expect_true(validate_range(0, min = 0))
  expect_true(validate_range(c(1, 2, 3), min = 1))
})

test_that("validate_range accepts numeric within specified max", {
  expect_true(validate_range(5, max = 10))
  expect_true(validate_range(10, max = 10))
  expect_true(validate_range(c(1, 2, 3), max = 5))
})

test_that("validate_range accepts numeric within specified range", {
  expect_true(validate_range(0.5, min = 0, max = 1))
  expect_true(validate_range(c(0.2, 0.5, 0.8), min = 0, max = 1))
  expect_true(validate_range(c(10, 20, 30), min = 10, max = 30))
})

test_that("validate_range rejects non-numeric input", {
  expect_error(
    validate_range("text"),
    "must be numeric"
  )

  expect_error(
    validate_range(c("a", "b")),
    "must be numeric"
  )

  expect_error(
    validate_range(list(1, 2)),
    "must be numeric"
  )
})

test_that("validate_range rejects values below minimum", {
  expect_error(
    validate_range(-1, min = 0),
    "must be >= 0"
  )

  expect_error(
    validate_range(c(1, 2, -5), min = 0),
    "must be >= 0"
  )

  expect_error(
    validate_range(5, min = 10),
    "must be >= 10"
  )
})

test_that("validate_range rejects values above maximum", {
  expect_error(
    validate_range(11, max = 10),
    "must be <= 10"
  )

  expect_error(
    validate_range(c(1, 2, 15), max = 10),
    "must be <= 10"
  )

  expect_error(
    validate_range(1.5, max = 1),
    "must be <= 1"
  )
})

test_that("validate_range uses custom arg_name in error messages", {
  expect_error(
    validate_range("text", arg_name = "alpha"),
    "alpha must be numeric"
  )

  expect_error(
    validate_range(-1, min = 0, arg_name = "opacity"),
    "opacity must be >= 0"
  )

  expect_error(
    validate_range(2, max = 1, arg_name = "ratio"),
    "ratio must be <= 1"
  )
})

test_that("validate_range handles NA values correctly", {
  # NA values should be skipped by na.rm = TRUE
  expect_true(validate_range(c(0.5, NA, 0.8), min = 0, max = 1))
  expect_true(validate_range(c(NA, 5, NA), min = 0, max = 10))
})

# ============================================
# validate_choice() - Tests for choice validation
# ============================================

test_that("validate_choice accepts valid choices", {
  expect_true(validate_choice("circle", c("circle", "square", "triangle")))
  expect_true(validate_choice("square", c("circle", "square", "triangle")))
  expect_true(validate_choice(1, c(1, 2, 3)))
  expect_true(validate_choice("solid", c("solid", "dashed", "dotted")))
})

test_that("validate_choice rejects invalid choices", {
  expect_error(
    validate_choice("hexagon", c("circle", "square", "triangle")),
    "must be one of: circle, square, triangle"
  )

  expect_error(
    validate_choice(4, c(1, 2, 3)),
    "must be one of: 1, 2, 3"
  )

  expect_error(
    validate_choice("wavy", c("solid", "dashed", "dotted")),
    "must be one of: solid, dashed, dotted"
  )
})

test_that("validate_choice uses custom arg_name in error messages", {
  expect_error(
    validate_choice("invalid", c("a", "b", "c"), arg_name = "shape"),
    "shape must be one of: a, b, c"
  )

  expect_error(
    validate_choice("x", c("yes", "no"), arg_name = "option"),
    "option must be one of: yes, no"
  )
})

# ============================================
# validate_length() - Tests for length validation
# ============================================

test_that("validate_length accepts correct length", {
  expect_true(validate_length(1:5, 5))
  expect_true(validate_length(c("a", "b", "c"), 3))
  expect_true(validate_length(rep(TRUE, 10), 10))
})

test_that("validate_length accepts single value when allow_single is TRUE", {
  expect_true(validate_length(1, 5, allow_single = TRUE))
  expect_true(validate_length("red", 10, allow_single = TRUE))
  expect_true(validate_length(TRUE, 100, allow_single = TRUE))
})

test_that("validate_length rejects single value when allow_single is FALSE", {
  expect_error(
    validate_length(1, 5, allow_single = FALSE),
    "must have length 5"
  )

  expect_error(
    validate_length("red", 10, allow_single = FALSE),
    "must have length 10"
  )
})

test_that("validate_length rejects incorrect length", {
  expect_error(
    validate_length(1:3, 5),
    "must have length 5 or 1"
  )

  expect_error(
    validate_length(c("a", "b"), 4),
    "must have length 4 or 1"
  )

  expect_error(
    validate_length(1:3, 5, allow_single = FALSE),
    "must have length 5"
  )
})

test_that("validate_length uses custom arg_name in error messages", {
  expect_error(
    validate_length(1:3, 5, arg_name = "colors"),
    "colors must have length 5 or 1"
  )

  expect_error(
    validate_length(c(1, 2), 10, arg_name = "sizes", allow_single = FALSE),
    "sizes must have length 10"
  )
})

# ============================================
# recycle_to_length() - Tests for recycling values
# ============================================

test_that("recycle_to_length returns same vector when length matches", {
  x <- 1:5
  result <- recycle_to_length(x, 5)
  expect_equal(result, x)
  expect_length(result, 5)

  x2 <- c("a", "b", "c")
  result2 <- recycle_to_length(x2, 3)
  expect_equal(result2, x2)
})

test_that("recycle_to_length replicates single value", {
  result <- recycle_to_length(1, 5)
  expect_equal(result, rep(1, 5))
  expect_length(result, 5)

  result2 <- recycle_to_length("red", 10)
  expect_equal(result2, rep("red", 10))
  expect_length(result2, 10)

  result3 <- recycle_to_length(TRUE, 3)
  expect_equal(result3, c(TRUE, TRUE, TRUE))
})

test_that("recycle_to_length uses rep_len for other lengths", {
  # Evenly divisible
  result <- recycle_to_length(c(1, 2), 6)
  expect_equal(result, c(1, 2, 1, 2, 1, 2))
  expect_length(result, 6)

  # Not evenly divisible - uses rep_len
  result2 <- recycle_to_length(c(1, 2, 3), 5)
  expect_equal(result2, c(1, 2, 3, 1, 2))
  expect_length(result2, 5)

  result3 <- recycle_to_length(c("a", "b"), 5)
  expect_equal(result3, c("a", "b", "a", "b", "a"))
})

test_that("recycle_to_length handles edge cases", {
  # Empty vector
  result <- recycle_to_length(numeric(0), 0)
  expect_length(result, 0)

  # NA values
  result2 <- recycle_to_length(NA, 3)
  expect_equal(result2, c(NA, NA, NA))

  # NULL with n=0 works
  result3 <- recycle_to_length(NULL, 0)
  expect_length(result3, 0)
})

# ============================================
# expand_param() - Tests for strict parameter expansion
# ============================================

test_that("expand_param replicates single value to target length", {
  result <- expand_param(5, 10)
  expect_equal(result, rep(5, 10))
  expect_length(result, 10)

  result2 <- expand_param("blue", 5)
  expect_equal(result2, rep("blue", 5))

  result3 <- expand_param(TRUE, 3)
  expect_equal(result3, c(TRUE, TRUE, TRUE))
})

test_that("expand_param returns vector unchanged when length matches", {
  x <- 1:5
  result <- expand_param(x, 5)
  expect_equal(result, x)

  x2 <- c("a", "b", "c")
  result2 <- expand_param(x2, 3)
  expect_equal(result2, x2)
})

test_that("expand_param throws error for incorrect lengths", {
  expect_error(
    expand_param(1:3, 5),
    "must be length 1 or 5, not 3"
  )

  expect_error(
    expand_param(c("a", "b"), 4),
    "must be length 1 or 4, not 2"
  )

  expect_error(
    expand_param(1:10, 5),
    "must be length 1 or 5, not 10"
  )
})

test_that("expand_param uses custom name in error messages", {
  expect_error(
    expand_param(1:3, 5, name = "node_color"),
    "node_color must be length 1 or 5, not 3"
  )

  expect_error(
    expand_param(c(1, 2), 10, name = "edge_width"),
    "edge_width must be length 1 or 10, not 2"
  )
})

test_that("expand_param handles single element target length", {
  result <- expand_param(5, 1)
  expect_equal(result, 5)
  expect_length(result, 1)
})

# ============================================
# resolve_aesthetic() - Tests for aesthetic resolution
# ============================================

test_that("resolve_aesthetic returns NULL when value and default are NULL", {
  result <- resolve_aesthetic(NULL)
  expect_null(result)

  result2 <- resolve_aesthetic(NULL, default = NULL)
  expect_null(result2)
})

test_that("resolve_aesthetic uses default when value is NULL", {
  result <- resolve_aesthetic(NULL, default = "red")
  expect_equal(result, "red")

  result2 <- resolve_aesthetic(NULL, default = 5)
  expect_equal(result2, 5)

  result3 <- resolve_aesthetic(NULL, default = c(1, 2, 3))
  expect_equal(result3, c(1, 2, 3))
})

test_that("resolve_aesthetic looks up column names in data", {
  df <- data.frame(
    color = c("red", "blue", "green"),
    size = c(1, 2, 3),
    label = c("A", "B", "C")
  )

  result <- resolve_aesthetic("color", data = df)
  expect_equal(result, c("red", "blue", "green"))

  result2 <- resolve_aesthetic("size", data = df)
  expect_equal(result2, c(1, 2, 3))

  result3 <- resolve_aesthetic("label", data = df)
  expect_equal(result3, c("A", "B", "C"))
})

test_that("resolve_aesthetic returns literal string when not a column name", {
  df <- data.frame(x = 1:3, y = 4:6)

  result <- resolve_aesthetic("red", data = df)
  expect_equal(result, "red")

  result2 <- resolve_aesthetic("notacolumn", data = df)
  expect_equal(result2, "notacolumn")
})

test_that("resolve_aesthetic recycles to target length", {
  result <- resolve_aesthetic(5, n = 10)
  expect_equal(result, rep(5, 10))
  expect_length(result, 10)

  result2 <- resolve_aesthetic("blue", n = 5)
  expect_equal(result2, rep("blue", 5))

  result3 <- resolve_aesthetic(c(1, 2), n = 6)
  expect_equal(result3, c(1, 2, 1, 2, 1, 2))
})

test_that("resolve_aesthetic with data and n works correctly", {
  df <- data.frame(color = c("red", "blue", "green"))

  # Column lookup with matching n
  result <- resolve_aesthetic("color", data = df, n = 3)
  expect_equal(result, c("red", "blue", "green"))
  expect_length(result, 3)
})

test_that("resolve_aesthetic returns non-string values directly", {
  result <- resolve_aesthetic(c(1, 2, 3))
  expect_equal(result, c(1, 2, 3))

  result2 <- resolve_aesthetic(TRUE)
  expect_true(result2)

  result3 <- resolve_aesthetic(c(TRUE, FALSE, TRUE))
  expect_equal(result3, c(TRUE, FALSE, TRUE))
})

test_that("resolve_aesthetic handles vector of strings without data", {
  result <- resolve_aesthetic(c("red", "blue", "green"))
  expect_equal(result, c("red", "blue", "green"))

  # Multiple strings are not looked up as column (only single string)
  result2 <- resolve_aesthetic(c("a", "b"), n = 4)
  expect_equal(result2, c("a", "b", "a", "b"))
})

test_that("resolve_aesthetic default is recycled to length n", {
  result <- resolve_aesthetic(NULL, default = "red", n = 5)
  expect_equal(result, rep("red", 5))
  expect_length(result, 5)
})

test_that("resolve_aesthetic handles NULL data gracefully", {
  result <- resolve_aesthetic("color", data = NULL, n = 3)
  expect_equal(result, rep("color", 3))
})

# ============================================
# Integration Tests - Multiple validations
# ============================================

test_that("validation functions work together in typical usage", {
  # Simulate typical splot parameter validation
  node_alpha <- 0.8
  expect_true(validate_range(node_alpha, 0, 1, "node_alpha"))

  edge_style <- "solid"
  expect_true(validate_choice(edge_style, c("solid", "dashed", "dotted"), "edge_style"))

  n_nodes <- 5
  colors <- "red"
  expect_true(validate_length(colors, n_nodes, "colors", allow_single = TRUE))
  recycled_colors <- recycle_to_length(colors, n_nodes)
  expect_length(recycled_colors, n_nodes)
})

test_that("expand_param and recycle_to_length differ in strictness", {
  # expand_param is strict - only length 1 or n
  expect_error(expand_param(1:3, 5))

  # recycle_to_length is permissive - any length via rep_len
  result <- recycle_to_length(1:3, 5)
  expect_length(result, 5)
})

test_that("resolve_aesthetic chains with validate functions", {
  df <- data.frame(
    alpha = c(0.5, 0.7, 0.9),
    shape = c("circle", "square", "triangle")
  )

  # Resolve and validate alpha
  alpha <- resolve_aesthetic("alpha", data = df, n = 3)
  expect_true(validate_range(alpha, 0, 1, "alpha"))

  # Resolve and validate each shape
  shapes <- resolve_aesthetic("shape", data = df, n = 3)
  for (s in shapes) {
    expect_true(validate_choice(s, c("circle", "square", "triangle", "diamond"), "shape"))
  }
})

# ============================================
# Edge Cases and Boundary Tests
# ============================================

test_that("validate_range handles Inf boundaries correctly", {
  expect_true(validate_range(1e100))  # default is -Inf to Inf
  expect_true(validate_range(-1e100))
  expect_true(validate_range(0, min = -Inf))
  expect_true(validate_range(0, max = Inf))
})

test_that("validate_color handles special R color values", {
  # Palette colors
  expect_true(validate_color(palette()[1]))

  # Colors function
  expect_true(validate_color(colors()[100]))
})

test_that("recycle_to_length with n=0 returns empty vector",
{
  result <- recycle_to_length(1:3, 0)
  expect_length(result, 0)
})

test_that("expand_param with n=1 and length-1 input works", {
  result <- expand_param(42, 1)
  expect_equal(result, 42)
  expect_length(result, 1)
})

test_that("validate_length with length-0 expected accepts empty input", {
  result <- validate_length(numeric(0), 0)
  expect_true(result)
})
