///|
pub enum SamplingStrategy {
  Uniform
  Grid
  Jittered
} derive(Debug, Eq)

///|
pub struct SamplingConfig {
  count : Int
  seed : UInt
  strategy : SamplingStrategy
  margin : Int
} derive(Debug, Eq)

///|
pub fn SamplingConfig::new(
  count~ : Int,
  seed~ : UInt,
  strategy? : SamplingStrategy = Uniform,
  margin? : Int = 0,
) -> SamplingConfig {
  { count: max_i(0, count), seed, strategy, margin: max_i(0, margin) }
}

///|
pub fn SamplingConfig::jittered(
  count : Int,
  seed : UInt,
  margin : Int,
) -> SamplingConfig {
  SamplingConfig::new(count~, seed~, strategy=Jittered, margin~)
}

///|
pub fn sample_points(size : Size, config : SamplingConfig) -> Array[Point] {
  let out : Array[Point] = []
  let count = config.count
  let margin_x = min_i(config.margin, max_i(0, size.width / 2))
  let margin_y = min_i(config.margin, max_i(0, size.height / 2))
  match config.strategy {
    Uniform => {
      let rng = Rng::new(config.seed)
      for _ in 0.. {
      let columns = max_i(1, (count * 2 + 1) / max_i(1, size.height))
      let rows = max_i(1, (count + columns - 1) / columns)
      for y in 0.. {
      let base = sample_points(
        size,
        SamplingConfig::new(
          count~,
          seed=config.seed,
          strategy=Grid,
          margin=config.margin,
        ),
      )
      let rng = Rng::new(config.seed + 17)
      for point in base {
        out.push(
          Point::new(
            x=clamp(
              point.x + rng.signed(2),
              low=0,
              high=max_i(0, size.width - 1),
            ),
            y=clamp(
              point.y + rng.signed(2),
              low=0,
              high=max_i(0, size.height - 1),
            ),
          ),
        )
      }
    }
  }
  out
}

///|
pub fn sample_rects(
  size : Size,
  config : SamplingConfig,
  min_size : Size,
  max_size : Size,
) -> Array[Rect] {
  let out : Array[Rect] = []
  let rng = Rng::new(config.seed)
  let min_width = max_i(1, min_size.width)
  let min_height = max_i(1, min_size.height)
  let max_width = max_i(min_width, min_i(max_size.width, size.width))
  let max_height = max_i(min_height, min_i(max_size.height, size.height))
  for _ in 0.. Rect {
  let dx = rng.signed(amount)
  let dy = rng.signed(amount)
  clamp_rect(rect.translate(dx, dy), size)
}

///|
pub fn jitter_boxes(
  boxes : Array[Box2D],
  seed : UInt,
  amount : Int,
  size : Size,
) -> Array[Box2D] {
  let rng = Rng::new(seed)
  let out : Array[Box2D] = []
  for box in boxes {
    out.push(
      Box2D::new(
        label=box.label,
        rect=jitter_rect(box.rect, rng, amount, size),
        occluded=box.occluded,
      ),
    )
  }
  out
}

///|
pub fn boxes_from_rects(
  rects : Array[Rect],
  label : Label,
  occluded? : Bool = false,
) -> Array[Box2D] {
  let out : Array[Box2D] = []
  for rect in rects {
    out.push(Box2D::new(label~, rect~, occluded~))
  }
  out
}

///|
pub fn render_sampled_detection_scene(
  size : Size,
  config : SamplingConfig,
) -> SceneOutput {
  let plan = ScenePlan::new(
    name="sampled_detection",
    kind=Detection,
    size~,
    seed=config.seed,
  )
  let image = gradient(size, horizontal=false)
  let rects = sample_rects(
    size,
    config,
    Size::new(width=4, height=4),
    Size::new(width=max_i(4, size.width / 3), height=max_i(4, size.height / 3)),
  )
  let label = Label::new(id=1, name="sampled")
  let boxes = boxes_from_rects(rects, label)
  for box in boxes {
    image.fill_rect(box.rect, Gray::new(80))
    image.draw_rect(box.rect, Gray::white())
  }
  SceneOutput::new(plan~, image~, boxes~)
}

///|
pub fn sampling_manifest(
  size : Size,
  configs : Array[SamplingConfig],
  prefix : String,
) -> DatasetManifest {
  let manifest = DatasetManifest::new(name="sampled-vision-fixtures")
  for i, config in configs {
    manifest.add(
      Asset::new(
        name="sampled_\{i}_\{size.width}x\{size.height}",
        image_path="\{prefix}/sampled_\{i}.pgm",
        annotation_path="\{prefix}/sampled_\{i}.json",
        seed=config.seed,
        tags=["detection", "sampled", "synthetic"],
      ),
    )
  }
  manifest
}