///|
pub(all) enum ElectricFieldKind {
  Constant
  Sinusoidal
  Gaussian
  Linear
  Pulse
} derive(Eq, Debug, ToJson)

///|
pub(all) struct ElectricFieldModel {
  kind : ElectricFieldKind
  amplitude : Double
  offset : Double
  wave_number : Double
  center : Double
  width : Double
  slope : Double
} derive(Debug, ToJson)

///|
pub fn constant_electric_field(value : Double) -> ElectricFieldModel {
  {
    kind: Constant,
    amplitude: value,
    offset: 0.0,
    wave_number: 0.0,
    center: 0.0,
    width: 1.0,
    slope: 0.0,
  }
}

///|
pub fn sinusoidal_electric_field(
  amplitude : Double,
  offset : Double,
  wave_number : Double,
  phase : Double,
) -> ElectricFieldModel {
  {
    kind: Sinusoidal,
    amplitude,
    offset,
    wave_number,
    center: phase,
    width: 1.0,
    slope: 0.0,
  }
}

///|
pub fn gaussian_electric_field(
  amplitude : Double,
  center : Double,
  width : Double,
  offset : Double,
) -> ElectricFieldModel {
  {
    kind: Gaussian,
    amplitude,
    offset,
    wave_number: 0.0,
    center,
    width,
    slope: 0.0,
  }
}

///|
pub fn linear_electric_field(
  offset : Double,
  slope : Double,
  center : Double,
) -> ElectricFieldModel {
  {
    kind: Linear,
    amplitude: 0.0,
    offset,
    wave_number: 0.0,
    center,
    width: 1.0,
    slope,
  }
}

///|
pub fn pulse_electric_field(
  amplitude : Double,
  center : Double,
  width : Double,
  offset : Double,
) -> ElectricFieldModel {
  {
    kind: Pulse,
    amplitude,
    offset,
    wave_number: 0.0,
    center,
    width,
    slope: 0.0,
  }
}

///|
pub fn electric_field_value(model : ElectricFieldModel, x : Double) -> Double {
  match model.kind {
    Constant => model.amplitude + model.offset
    Sinusoidal =>
      model.offset +
      model.amplitude * @math.cos(model.wave_number * x + model.center)
    Gaussian => {
      let scale = safe_ratio(x - model.center, model.width, 0.0)
      model.offset + model.amplitude * @math.exp(-0.5 * scale * scale)
    }
    Linear => model.offset + model.slope * (x - model.center)
    Pulse =>
      if (x - model.center).abs() <= model.width * 0.5 {
        model.offset + model.amplitude
      } else {
        model.offset
      }
  }
}

///|
pub fn electric_field_values(
  grid : Grid1D,
  model : ElectricFieldModel,
) -> Array[Double] {
  Array::makei(grid.cells, fn(i) {
    electric_field_value(model, grid.position(i))
  })
}

///|
pub fn electric_potential_value(
  model : ElectricFieldModel,
  x : Double,
) -> Double {
  match model.kind {
    Constant => -(model.amplitude + model.offset) * x
    Sinusoidal =>
      if model.wave_number == 0.0 {
        -(model.offset + model.amplitude) * x
      } else {
        -model.amplitude * @math.sin(model.wave_number * x) / model.wave_number -
        model.offset * x
      }
    Gaussian =>
      model.offset * -x -
      model.amplitude *
      (x - model.center) *
      @math.exp(
        -0.5 *
        safe_ratio(
          (x - model.center) * (x - model.center),
          model.width * model.width,
          0.0,
        ),
      )
    Linear =>
      -model.offset * x -
      0.5 * model.slope * (x - model.center) * (x - model.center)
    Pulse =>
      -model.offset * x -
      (if x > model.center { model.amplitude * model.width } else { 0.0 })
  }
}

///|
pub fn field_model_mean(grid : Grid1D, model : ElectricFieldModel) -> Double {
  mean(electric_field_values(grid, model))
}

///|
pub fn field_model_energy(grid : Grid1D, model : ElectricFieldModel) -> Double {
  let values = electric_field_values(grid, model)
  0.5 *
  vacuum_permittivity *
  grid.dx *
  sum_values(values.map(fn(value) { value * value }))
}

///|
pub fn combine_electric_models(
  first : ElectricFieldModel,
  second : ElectricFieldModel,
) -> ElectricFieldModel {
  {
    kind: Linear,
    amplitude: first.amplitude + second.amplitude,
    offset: first.offset + second.offset,
    wave_number: first.wave_number + second.wave_number,
    center: first.center,
    width: first.width,
    slope: first.slope + second.slope,
  }
}

///|
pub fn field_model_force(
  model : ElectricFieldModel,
  charge : Double,
  x : Double,
) -> Double {
  charge * electric_field_value(model, x)
}

///|
pub fn field_model_work(
  model : ElectricFieldModel,
  charge : Double,
  start : Double,
  stop : Double,
  samples : Int,
) -> Double {
  if samples <= 0 {
    0.0
  } else {
    let grid = Grid1D::new(samples, (stop - start).abs())
    let values = electric_field_values(grid, model)
    charge * sum_values(values) * grid.dx
  }
}

///|
pub fn field_model_name(model : ElectricFieldModel) -> String {
  match model.kind {
    Constant => "constant"
    Sinusoidal => "sinusoidal"
    Gaussian => "gaussian"
    Linear => "linear"
    Pulse => "pulse"
  }
}