///|
pub(all) struct MagneticField3D {
  origin : Vector3
  uniform : Vector3
  gradient : Vector3
} derive(Debug, ToJson)

///|
pub fn uniform_magnetic_field(field : Vector3) -> MagneticField3D {
  { origin: Vector3::zero(), uniform: field, gradient: Vector3::zero() }
}

///|
pub fn linear_magnetic_field(
  origin : Vector3,
  uniform : Vector3,
  gradient : Vector3,
) -> MagneticField3D {
  { origin, uniform, gradient }
}

///|
pub fn MagneticField3D::sample(
  field : MagneticField3D,
  position : Vector3,
) -> Vector3 {
  let offset = position.sub(field.origin)
  {
    x: field.uniform.x + field.gradient.x * offset.x,
    y: field.uniform.y + field.gradient.y * offset.y,
    z: field.uniform.z + field.gradient.z * offset.z,
  }
}

///|
pub fn MagneticField3D::magnitude(
  field : MagneticField3D,
  position : Vector3,
) -> Double {
  field.sample(position).norm()
}

///|
pub fn MagneticField3D::flux_through_area(
  field : MagneticField3D,
  position : Vector3,
  normal : Vector3,
  area : Double,
) -> Double {
  field.sample(position).dot(normal.normalized()) * area
}

///|
pub fn dipole_magnetic_field(
  moment : Vector3,
  position : Vector3,
  coefficient : Double,
) -> Vector3 {
  let radius = position.norm()
  if radius == 0.0 {
    Vector3::zero()
  } else {
    let unit = position.scale(1.0 / radius)
    let dot = moment.dot(unit)
    let factor = coefficient / (radius * radius * radius)
    unit.scale(3.0 * dot * factor).sub(moment.scale(factor))
  }
}

///|
pub fn magnetic_force(
  charge : Double,
  velocity : Vector3,
  magnetic : Vector3,
) -> Vector3 {
  velocity.cross(magnetic).scale(charge)
}

///|
pub fn electric_force(charge : Double, electric : Vector3) -> Vector3 {
  electric.scale(charge)
}

///|
pub fn lorentz_force(
  charge : Double,
  velocity : Vector3,
  electric : Vector3,
  magnetic : Vector3,
) -> Vector3 {
  electric_force(charge, electric).add(
    magnetic_force(charge, velocity, magnetic),
  )
}

///|
pub fn field_line_step(
  position : Vector3,
  field : MagneticField3D,
  step : Double,
) -> Vector3 {
  position.add(field.sample(position).normalized().scale(step))
}

///|
pub fn trace_field_line(
  position : Vector3,
  field : MagneticField3D,
  step : Double,
  count : Int,
) -> Array[Vector3] {
  let output : Array[Vector3] = []
  let mut current = position
  for _ in 0.. Double {
  0.5 * field.dot(field) / vacuum_permittivity
}

///|
pub fn magnetic_pressure(field : Vector3) -> Double {
  magnetic_energy_density(field)
}

///|
pub fn gyroradius(
  mass : Double,
  speed : Double,
  charge : Double,
  magnetic : Double,
) -> Double {
  let denominator = charge.abs() * magnetic.abs()
  if denominator == 0.0 {
    0.0
  } else {
    mass * speed / denominator
  }
}