# ==============================================================================
# Additional tests for morph utility functions to increase coverage
# ==============================================================================

# ------------------------------------------------------------------------------
# .gif_delay_from_fps edge cases
# ------------------------------------------------------------------------------

test_that(".gif_delay_from_fps handles edge cases", {
  skip_on_cran()
  # NaN and Inf should trigger default 10 fps (delay = 10 centiseconds)
  expect_equal(couplr:::.gif_delay_from_fps(NaN), 10L)
  expect_equal(couplr:::.gif_delay_from_fps(Inf), 10L)
  # Zero or negative should also default
  expect_equal(couplr:::.gif_delay_from_fps(0), 10L)
  expect_equal(couplr:::.gif_delay_from_fps(-5), 10L)
})

test_that(".gif_delay_from_fps handles fractional fps", {
  skip_on_cran()
  # 25.5 fps should round to 26
  expect_equal(couplr:::.gif_delay_from_fps(25.5), 4L)  # 100/26 = 3.8 -> 4
  # 15.4 fps should round to 15
  expect_equal(couplr:::.gif_delay_from_fps(15.4), 7L)  # 100/15 = 6.67 -> 7
})

# ------------------------------------------------------------------------------
# .to_array_rgb edge cases
# ------------------------------------------------------------------------------

test_that(".to_array_rgb handles different magick color formats", {
  skip_on_cran()
  skip_if_not_installed("magick")

  # Blue image
  img <- magick::image_blank(2, 2, color = "blue")
  result <- couplr:::.to_array_rgb(img)
  expect_equal(dim(result), c(2, 2, 3))
  expect_true(all(result[,,3] == 255))  # Blue channel
  expect_true(all(result[,,1] == 0))    # Red channel

  # White image
  img <- magick::image_blank(2, 2, color = "white")
  result <- couplr:::.to_array_rgb(img)
  expect_true(all(result == 255))

  # Black image
  img <- magick::image_blank(2, 2, color = "black")
  result <- couplr:::.to_array_rgb(img)
  expect_true(all(result == 0))
})

test_that(".to_array_rgb handles hex colors", {
  skip_on_cran()
  skip_if_not_installed("magick")

  img <- magick::image_blank(2, 2, color = "#FF8800")
  result <- couplr:::.to_array_rgb(img)
  expect_equal(dim(result), c(2, 2, 3))
  expect_true(all(result[,,1] == 255))  # Red
  expect_true(all(result[,,2] == 136))  # Green (0x88 = 136)
  expect_true(all(result[,,3] == 0))    # Blue
})

# ------------------------------------------------------------------------------
# .clamp_rgb additional edge cases
# ------------------------------------------------------------------------------

test_that(".clamp_rgb handles 3D arrays", {
  skip_on_cran()
  arr <- array(c(-100, 0, 128, 300, 50, 200, -10, 260), dim = c(2, 2, 2))
  result <- couplr:::.clamp_rgb(arr)

  expect_equal(dim(result), c(2, 2, 2))
  expect_true(all(result >= 0L))
  expect_true(all(result <= 255L))
})

test_that(".clamp_rgb handles single value", {
  skip_on_cran()
  expect_equal(couplr:::.clamp_rgb(300), 255L)
  expect_equal(couplr:::.clamp_rgb(-100), 0L)
  expect_equal(couplr:::.clamp_rgb(128), 128L)
})

# ------------------------------------------------------------------------------
# .palette_pairs_lap tests
# ------------------------------------------------------------------------------

test_that(".palette_pairs_lap matches colors by LAP", {
  skip_on_cran()
  info <- list(
    colorsA_rgb = matrix(c(255, 0, 0,
                           0, 255, 0), nrow = 2, ncol = 3, byrow = TRUE),
    colorsB_rgb = matrix(c(250, 5, 0,  # Close to red
                           5, 250, 5), nrow = 2, ncol = 3, byrow = TRUE),  # Close to green
    countsA = c(10, 5),
    countsB = c(8, 7),
    color_dist = matrix(c(0.1, 0.9,
                          0.9, 0.1), nrow = 2, ncol = 2)
  )

  result <- couplr:::.palette_pairs_lap(info, method = "hungarian")

  expect_equal(nrow(result), 2)
  expect_true(all(c("ia", "ib", "k") %in% names(result)))
})

test_that(".palette_pairs_lap handles empty matrices", {
  skip_on_cran()
  info <- list(
    countsA = integer(0),
    countsB = integer(0),
    color_dist = matrix(nrow = 0, ncol = 0)
  )

  result <- couplr:::.palette_pairs_lap(info)
  expect_equal(nrow(result), 0)
})

# ------------------------------------------------------------------------------
# .patch_cost_matrix tests
# ------------------------------------------------------------------------------

test_that(".patch_cost_matrix computes cost with color and spatial", {
  skip_on_cran()
  patches_a <- list(
    colors = matrix(c(255, 0, 0, 0, 255, 0), nrow = 2, ncol = 3, byrow = TRUE),
    centers = matrix(c(0, 0, 10, 10), nrow = 2, ncol = 2, byrow = TRUE)
  )
  patches_b <- list(
    colors = matrix(c(250, 5, 5, 5, 250, 5), nrow = 2, ncol = 3, byrow = TRUE),
    centers = matrix(c(1, 1, 11, 11), nrow = 2, ncol = 2, byrow = TRUE)
  )

  result <- couplr:::.patch_cost_matrix(patches_a, patches_b, alpha = 1, beta = 0.1, H = 20, W = 20)

  expect_equal(dim(result), c(2, 2))
  expect_true(is.numeric(result))
  expect_true(all(is.finite(result)))
})

test_that(".patch_cost_matrix handles missing H and W", {
  skip_on_cran()
  # Use 2x2 patches to get a proper matrix (1x1 returns scalar)
  patches_a <- list(
    colors = matrix(c(255, 0, 0, 0, 255, 0), nrow = 2, ncol = 3, byrow = TRUE),
    centers = matrix(c(5, 5, 15, 15), nrow = 2, ncol = 2, byrow = TRUE)
  )
  patches_b <- list(
    colors = matrix(c(250, 10, 10, 10, 250, 10), nrow = 2, ncol = 3, byrow = TRUE),
    centers = matrix(c(6, 6, 16, 16), nrow = 2, ncol = 2, byrow = TRUE)
  )

  result <- couplr:::.patch_cost_matrix(patches_a, patches_b, alpha = 1, beta = 0.1)

  expect_equal(dim(result), c(2, 2))
  expect_true(is.numeric(result))
})

# ------------------------------------------------------------------------------
# .expand_patch_assignment tests
# ------------------------------------------------------------------------------

test_that(".expand_patch_assignment expands patches to pixels", {
  skip_on_cran()
  patch_assign <- list(2L, 1L)  # Patch 1 -> B patch 2, Patch 2 -> B patch 1
  patches_a <- list(
    indices = list(c(1L, 2L), c(3L, 4L))
  )
  patches_b <- list(
    indices = list(c(5L, 6L), c(7L, 8L))
  )
  N <- 4

  result <- couplr:::.expand_patch_assignment(patch_assign, patches_a, patches_b, N)

  expect_equal(length(result), 4)
  expect_equal(result[1], 7L)  # A index 1 -> B index from patch 2
  expect_equal(result[2], 8L)
  expect_equal(result[3], 5L)  # A index 3 -> B index from patch 1
  expect_equal(result[4], 6L)
})

test_that(".expand_patch_assignment handles missing assignments", {
  skip_on_cran()
  patch_assign <- list(NA, 1L)
  patches_a <- list(
    indices = list(c(1L, 2L), c(3L, 4L))
  )
  patches_b <- list(
    indices = list(c(5L, 6L))
  )
  N <- 4

  result <- couplr:::.expand_patch_assignment(patch_assign, patches_a, patches_b, N)

  expect_equal(result[1], -1L)  # Not assigned
  expect_equal(result[2], -1L)
  expect_equal(result[3], 5L)   # Assigned
  expect_equal(result[4], 6L)
})

# ------------------------------------------------------------------------------
# .build_spatial_assignments_for_pairs tests
# ------------------------------------------------------------------------------

test_that(".build_spatial_assignments_for_pairs handles empty pairs", {
  skip_on_cran()
  info <- list(groupsA = list(), groupsB = list())
  pairs <- data.frame(ia = integer(), ib = integer(), k = integer())

  result <- couplr:::.build_spatial_assignments_for_pairs(info, pairs, H = 4, W = 4)

  expect_equal(length(result$i_idx), 0)
  expect_equal(length(result$j_idx), 0)
})

# ------------------------------------------------------------------------------
# .lap_assign edge cases
# ------------------------------------------------------------------------------

test_that(".lap_assign handles larger matrices", {
  skip_on_cran()
  cost <- matrix(runif(25), 5, 5)

  result <- couplr:::.lap_assign(cost, method = "hungarian")

  expect_equal(length(result), 5)
  expect_true(all(result >= 0))
  expect_true(all(result <= 4))
  expect_equal(length(unique(result)), 5)  # Bijection
})

test_that(".lap_assign handles rectangular matrices", {
  skip_on_cran()
  cost <- matrix(runif(12), 3, 4)

  result <- couplr:::.lap_assign(cost, method = "jv")

  expect_equal(length(result), 3)
  expect_true(all(result >= 0))
  expect_true(all(result <= 3))
})

# ------------------------------------------------------------------------------
# .exact_cost_and_solve tests
# ------------------------------------------------------------------------------

test_that(".exact_cost_and_solve computes and solves assignment", {
  skip_on_cran()
  H <- 2
  W <- 2
  N <- H * W
  A_planar <- rep(c(255, 0, 0), each = N)  # All red
  B_planar <- c(rep(c(255, 0, 0), each = 1), rep(c(0, 255, 0), each = 1),
                rep(c(0, 0, 255), each = 1), rep(c(128, 128, 128), each = 1))
  # Reshape B_planar to be planar format
  B_planar <- c(255, 0, 0, 128, 0, 255, 0, 128, 0, 0, 255, 128)

  result <- couplr:::.exact_cost_and_solve(A_planar, B_planar, H, W, alpha = 1, beta = 0)

  expect_equal(length(result), N)
  expect_true(all(result >= 1))
  expect_true(all(result <= N))
})

# ------------------------------------------------------------------------------
# .assemble_assignment edge cases
# ------------------------------------------------------------------------------

test_that(".assemble_assignment handles mismatched lengths", {
  skip_on_cran()
  N <- 5
  i_idx <- c(1L, 2L, 3L)
  j_idx <- c(4L, 5L)  # Shorter than i_idx

  result <- couplr:::.assemble_assignment(N, i_idx, j_idx)

  expect_equal(length(result), N)
  # Only first 2 should be assigned
  expect_true(result[1] > 0)
  expect_true(result[2] > 0)
  expect_equal(result[3], -1L)
})

test_that(".assemble_assignment handles large indices", {
  skip_on_cran()
  N <- 10
  i_idx <- c(1L, 5L, 10L)
  j_idx <- c(2L, 6L, 9L)

  result <- couplr:::.assemble_assignment(N, i_idx, j_idx)

  expect_equal(result[1], 2L)
  expect_equal(result[5], 6L)
  expect_equal(result[10], 9L)
})

# ------------------------------------------------------------------------------
# .fill_unassigned_identity edge cases
# ------------------------------------------------------------------------------

test_that(".fill_unassigned_identity handles all unassigned", {
  skip_on_cran()
  assign <- rep(-1L, 5)

  result <- couplr:::.fill_unassigned_identity(assign)

  expect_equal(result, 1:5)
})

test_that(".fill_unassigned_identity handles alternating pattern", {
  skip_on_cran()
  assign <- c(10L, -1L, 10L, -1L, 10L)

  result <- couplr:::.fill_unassigned_identity(assign)

  expect_equal(result[1], 10L)
  expect_equal(result[2], 2L)  # Filled with own index
  expect_equal(result[3], 10L)
  expect_equal(result[4], 4L)  # Filled with own index
  expect_equal(result[5], 10L)
})
