///|
pub(all) struct PicConfig {
  grid : Grid1D
  dt : Double
  steps : Int
  shape : ShapeKind
  boundary : Boundary1D
} derive(Debug, ToJson)

///|
pub fn PicConfig::new(grid : Grid1D, dt : Double, steps : Int) -> PicConfig {
  { grid, dt, steps: steps.max(0), shape: CIC, boundary: Periodic }
}

///|
pub fn PicConfig::validate(config : PicConfig) -> ValidationReport {
  let grid_report = validate_grid(config.grid.cells, config.grid.length)
  if !grid_report.valid {
    grid_report
  } else if config.dt <= 0.0 {
    invalid_report("dt", "time step must be positive", config.dt)
  } else {
    validate_count(config.steps + 1, "steps")
  }
}

///|
pub fn PicConfig::with_shape(
  config : PicConfig,
  shape : ShapeKind,
) -> PicConfig {
  { ..config, shape, }
}

///|
pub fn PicConfig::with_boundary(
  config : PicConfig,
  boundary : Boundary1D,
) -> PicConfig {
  { ..config, boundary, }
}

///|
pub fn pic_config_summary(config : PicConfig) -> String {
  "cells=\{config.grid.cells}\nlength=\{config.grid.length}\ndt=\{config.dt}\nsteps=\{config.steps}\nshape=\{shape_name(config.shape)}\n"
}

///|
pub(all) struct SamplingConfig {
  count : Int
  seed : Int
  drift : Double
  spread : Double
  profile : Profile1D
} derive(Debug, ToJson)

///|
pub fn SamplingConfig::new(
  count : Int,
  seed : Int,
  drift : Double,
  spread : Double,
) -> SamplingConfig {
  {
    count: count.max(0),
    seed,
    drift,
    spread: spread.max(0.0),
    profile: profile_constant(1.0),
  }
}

///|
pub fn SamplingConfig::validate(config : SamplingConfig) -> ValidationReport {
  if config.count < 0 {
    invalid_report(
      "count",
      "sample count cannot be negative",
      config.count.to_double(),
    )
  } else {
    validate_non_negative(config.spread, "spread")
  }
}

///|
pub fn SamplingConfig::sample(
  config : SamplingConfig,
  grid : Grid1D,
) -> Array[Particle] {
  sample_particles(grid, config.count, config.seed, config.drift, config.spread)
}

///|
pub fn sampling_config_summary(config : SamplingConfig) -> String {
  "count=\{config.count}\nseed=\{config.seed}\ndrift=\{config.drift}\nspread=\{config.spread}\n"
}

///|
pub fn validate_simulation_inputs(
  grid : Grid1D,
  dt : Double,
  particles : Int,
  steps : Int,
) -> ValidationReport {
  let grid_report = validate_grid(grid.cells, grid.length)
  if !grid_report.valid {
    grid_report
  } else if dt <= 0.0 {
    invalid_report("dt", "time step must be positive", dt)
  } else if particles < 0 {
    invalid_report(
      "particles",
      "particle count cannot be negative",
      particles.to_double(),
    )
  } else {
    validate_count(steps + 1, "steps")
  }
}

///|
pub fn config_to_csv(config : PicConfig) -> String {
  "field,value\n\ncells,\{config.grid.cells}\nlength,\{config.grid.length}\ndt,\{config.dt}\nsteps,\{config.steps}\nshape,\{shape_name(config.shape)}\n"
}