test_that("plot_dose_response_curves returns a ggplot with fitted curves", {
  one <- subset(
    screen_doseresponse,
    drug_name == "DrugA" & experiment_type == "sample"
  )
  p <- plot_dose_response_curves(one, drug = "DrugA")

  expect_s3_class(p, "ggplot")
  # observed points plus fitted lines for both conditions
  geoms <- vapply(p$layers, function(l) class(l$geom)[1], character(1))
  expect_true("GeomPoint" %in% geoms)
  expect_true("GeomLine" %in% geoms)
  # log10 concentration axis
  expect_true(any(vapply(
    p$scales$scales, function(s) "x" %in% s$aesthetics, logical(1)
  )))
})

test_that("plot_dose_response_curves errors on missing columns", {
  expect_error(
    plot_dose_response_curves(data.frame(a = 1, b = 2)),
    "not found"
  )
})

test_that("plot_dose_response_curves errors for an unknown drug", {
  expect_error(
    plot_dose_response_curves(screen_doseresponse, drug = "DoesNotExist"),
    "No rows found"
  )
})

test_that("plot_control_scatter returns a ggplot with both control groups", {
  p <- plot_control_scatter(screen_plate_qc)
  expect_s3_class(p, "ggplot")
  groups <- unique(as.character(p$data$control))
  expect_setequal(groups, c("Negative control", "Positive control"))
})

test_that("plot_control_scatter errors when control columns are absent", {
  expect_error(
    plot_control_scatter(data.frame(plateID = "P1"), plate_col = "plateID"),
    "No negative- or positive-control columns"
  )
})

test_that("plot_qc_metric_trend flags failing plates", {
  p <- plot_qc_metric_trend(screen_plate_qc, metric = "z_prime",
    threshold = 0.5)
  expect_s3_class(p, "ggplot")
  expected_bad <- sum(screen_plate_qc$z_prime < 0.5)
  expect_equal(sum(p$data$flag == "Bad"), expected_bad)
})

test_that("plot_qc_metric_trend respects direction = 'max'", {
  df <- data.frame(
    plateID = c("P1", "P2", "P3"),
    cv_negative_ctrl = c(5, 12, 8)
  )
  p <- plot_qc_metric_trend(df, metric = "cv_negative_ctrl",
    threshold = 10, direction = "max")
  # only the plate above the cut-off (12) should fail
  expect_equal(as.character(p$data$flag), c("Normal", "Bad", "Normal"))
})

test_that("plot_qc_metric_trend can use a precomputed flag column", {
  df <- data.frame(
    plateID = c("P1", "P2"),
    z_prime = c(0.7, 0.3),
    z_primer_flag = c("Normal", "Bad")
  )
  p <- plot_qc_metric_trend(df, metric = "z_prime", flag_col = "z_primer_flag")
  expect_equal(as.character(p$data$flag), c("Normal", "Bad"))
})

test_that("plot_qc_metric_trend errors on a missing metric column", {
  expect_error(
    plot_qc_metric_trend(screen_plate_qc, metric = "not_a_column"),
    "not found"
  )
})

test_that("plot_qc_metric_circular draws without error", {
  skip_if_not_installed("circlize")
  tmp <- tempfile(fileext = ".pdf")
  grDevices::pdf(tmp)
  on.exit({
    grDevices::dev.off()
    unlink(tmp)
  })
  expect_invisible(
    suppressMessages(plot_qc_metric_circular(screen_plate_qc))
  )
})

test_that("plot_qc_metric_circular errors on missing columns", {
  skip_if_not_installed("circlize")
  expect_error(
    plot_qc_metric_circular(data.frame(experimentID = "E", plateNumber = 1)),
    "not found"
  )
})

test_that("plot_qc_circular_heatmap draws without error", {
  skip_if_not_installed("circlize")
  tmp <- tempfile(fileext = ".pdf")
  grDevices::pdf(tmp)
  on.exit({
    grDevices::dev.off()
    unlink(tmp)
  })
  expect_invisible(
    suppressMessages(plot_qc_circular_heatmap(screen_plate_qc))
  )
})

test_that("plot_qc_circular_heatmap errors when no value columns match", {
  skip_if_not_installed("circlize")
  expect_error(
    plot_qc_circular_heatmap(
      data.frame(plateID = "P1", experimentID = "E1"),
      value_cols = character(0)
    ),
    "No control-value columns"
  )
})

test_that("plot_plate_heatmap builds a full 96-well grid", {
  p <- plot_plate_heatmap(
    subset(screen_plate_layout, plateID == "EXP99_Gy0")
  )
  expect_s3_class(p, "ggplot")
  # 8 rows x 12 columns, every well populated
  expect_equal(nrow(p$data), 96)
  expect_equal(sum(!is.na(p$data$value)), 96)
  expect_setequal(levels(p$data$row), LETTERS[1:8])
})

test_that("plot_plate_heatmap facets when several plates are present", {
  p <- plot_plate_heatmap(screen_plate_layout, fill = "experiment_type")
  expect_s3_class(p, "ggplot")
  expect_true("plate" %in% names(p$data))
  expect_equal(nrow(p$data), 192)
})

test_that("plot_plate_heatmap parses a single well-identifier column", {
  df <- data.frame(
    well = c("A1", "B2", "H12"),
    normalized_cell_count = c(1, 0.5, 0.05)
  )
  p <- plot_plate_heatmap(df, well_col = "well")
  expect_s3_class(p, "ggplot")
  filled <- p$data[!is.na(p$data$value), ]
  expect_equal(nrow(filled), 3)
})

test_that("plot_plate_heatmap circles controls when control_col is given", {
  has_points <- function(p) {
    any(vapply(p$layers, function(l) inherits(l$geom, "GeomPoint"), logical(1)))
  }
  # No control_col: no circle layer (backward compatible).
  p0 <- plot_plate_heatmap(subset(screen_plate_layout, plateID == "EXP99_Gy0"))
  expect_false(has_points(p0))

  # With control_col: a point layer marks NC/PC wells.
  p1 <- plot_plate_heatmap(subset(screen_plate_layout, plateID == "EXP99_Gy0"),
    control_col = "experiment_type")
  expect_true(has_points(p1))
  built <- ggplot2::ggplot_build(p1)$data
  # one circle per control well on the 96-well plate
  sub <- subset(screen_plate_layout, plateID == "EXP99_Gy0")
  n_ctrl <- sum(sub$experiment_type %in% c("negative_ctrl", "positive_ctrl"))
  expect_equal(nrow(built[[2]]), n_ctrl)
})

test_that("plot_plate_heatmap circles controls on a 384-well plate", {
  data("hts_plate_384", package = "diffHTS", envir = environment())
  p <- plot_plate_heatmap(hts_plate_384, fill = "signal", well_col = "well",
    control_col = "well_type", plate_col = "plate_id", plate_type = 384)
  expect_s3_class(p, "ggplot")
  # Full 384 grid: every well (including the buffer ring) carries a reading.
  expect_equal(nrow(p$data), 384)
  # The outer buffer ring is populated with a low, no-cell reading, not NA.
  expect_equal(sum(is.na(p$data$value)), 0)
  built <- ggplot2::ggplot_build(p)$data
  # 12 NC + 12 PC (diagonal in columns 3 and 22) circled
  expect_equal(nrow(built[[2]]), 24)
})

test_that("plot_plate_heatmap_raw and _inhibition circle controls", {
  has_points <- function(p) {
    any(vapply(p$layers, function(l) inherits(l$geom, "GeomPoint"), logical(1)))
  }
  raw <- baseline_subtract(read_hts_plate(hts_primary_raw))
  expect_true(has_points(plot_plate_heatmap_raw(raw, plate = "P01")))
  norm <- norm_by_control(raw)
  expect_true(has_points(plot_plate_heatmap_inhibition(norm, plate = "P01")))
})

test_that("plot_plate_heatmap errors on a missing control_col", {
  expect_error(
    plot_plate_heatmap(subset(screen_plate_layout, plateID == "EXP99_Gy0"),
      control_col = "not_a_column"),
    "not found"
  )
})
