!> `bem_app_config_parser` の入力検証・物理量導出手続きを実装する submodule。
submodule (bem_app_config_parser) bem_app_config_parser_validate
  implicit none
contains

  !> `batch_duration` 系パラメータの排他条件と値域を検証し、確定値を反映する。
  module procedure resolve_batch_duration
    real(dp) :: batch_duration

    if (cfg%sim%has_batch_duration .and. cfg%sim%has_batch_duration_step) then
      error stop 'sim.batch_duration and sim.batch_duration_step cannot be used together.'
    end if

    if (cfg%sim%has_batch_duration_step) then
      if (.not. ieee_is_finite(cfg%sim%batch_duration_step) .or. cfg%sim%batch_duration_step <= 0.0d0) then
        error stop 'sim.batch_duration_step must be > 0.'
      end if
      if (.not. ieee_is_finite(cfg%sim%dt) .or. cfg%sim%dt <= 0.0d0) then
        error stop 'sim.dt must be > 0 when sim.batch_duration_step is set.'
      end if
      batch_duration = cfg%sim%dt * cfg%sim%batch_duration_step
      if (.not. ieee_is_finite(batch_duration) .or. batch_duration <= 0.0d0) then
        error stop 'sim.batch_duration_step produced invalid sim.batch_duration.'
      end if
      cfg%sim%batch_duration = batch_duration
      cfg%sim%has_batch_duration = .true.
    else if (cfg%sim%has_batch_duration) then
      if (.not. ieee_is_finite(cfg%sim%batch_duration)) then
        error stop 'sim.batch_duration must be finite.'
      end if
    end if
  end procedure resolve_batch_duration

  !> `reservoir_face` 粒子種の必須項目と幾何条件を検証する。
  module procedure validate_reservoir_species
    integer :: axis, axis_t1, axis_t2
    real(dp) :: boundary_value, area
    real(dp) :: number_density_m3, temperature_k, gamma_in, w_particle
    real(dp) :: inward_normal(3)
    type(particle_species_spec) :: spec

    spec = cfg%particle_species(species_idx)

    if (spec%has_npcls_per_step) then
      error stop 'particles.species.npcls_per_step is auto-computed for reservoir_face.'
    end if
    if (abs(spec%emit_current_density_a_m2) > 0.0d0 .or. spec%rays_per_batch /= 0_i32 .or. &
        spec%has_ray_direction .or. spec%has_deposit_opposite_charge_on_emit) then
      error stop 'photo_raycast keys are not allowed for reservoir_face.'
    end if
    if (spec%has_w_particle .and. spec%has_target_macro_particles_per_batch) then
      error stop 'reservoir_face does not allow both w_particle and target_macro_particles_per_batch.'
    end if
    if (.not. spec%has_w_particle .and. .not. spec%has_target_macro_particles_per_batch) then
      error stop 'reservoir_face requires either w_particle or target_macro_particles_per_batch.'
    end if
    if (spec%has_w_particle) then
      if (spec%w_particle <= 0.0d0) error stop 'particles.species.w_particle must be > 0 for reservoir_face.'
    end if
    if (spec%has_target_macro_particles_per_batch) then
      if (spec%target_macro_particles_per_batch == 0_i32 .or. spec%target_macro_particles_per_batch < -1_i32) then
        error stop 'particles.species.target_macro_particles_per_batch must be > 0 or -1.'
      end if
      if (spec%target_macro_particles_per_batch == -1_i32) then
        if (species_idx == 1) then
          error stop 'particles.species[1].target_macro_particles_per_batch cannot be -1.'
        end if
        if (.not. cfg%particle_species(1)%enabled) then
          error stop 'target_macro_particles_per_batch=-1 requires particles.species[1] to be enabled.'
        end if
        if (trim(lower(cfg%particle_species(1)%source_mode)) /= 'reservoir_face') then
          error stop 'target_macro_particles_per_batch=-1 requires particles.species[1].source_mode="reservoir_face".'
        end if
        if (.not. cfg%particle_species(1)%has_w_particle .or. cfg%particle_species(1)%w_particle <= 0.0d0) then
          error stop 'target_macro_particles_per_batch=-1 requires species[1] to resolve a positive w_particle.'
        end if
      end if
    end if
    if (.not. cfg%sim%use_box) then
      error stop 'particles.species.source_mode="reservoir_face" requires sim.use_box = true.'
    end if
    if (cfg%sim%batch_duration <= 0.0d0) then
      error stop 'sim.batch_duration must be > 0 for reservoir_face.'
    end if
    if (spec%has_number_density_cm3 .and. spec%has_number_density_m3) then
      error stop 'Specify either number_density_cm3 or number_density_m3, not both.'
    end if
    if (.not. spec%has_number_density_cm3 .and. .not. spec%has_number_density_m3) then
      error stop 'reservoir_face requires number_density_cm3 or number_density_m3.'
    end if
    if (spec%has_number_density_cm3) then
      if (spec%number_density_cm3 <= 0.0d0) error stop 'number_density_cm3 must be > 0.'
    else
      if (spec%number_density_m3 <= 0.0d0) error stop 'number_density_m3 must be > 0.'
    end if
    if (spec%has_temperature_ev .and. spec%has_temperature_k) then
      error stop 'Specify either temperature_ev or temperature_k, not both.'
    end if
    if (spec%has_temperature_ev) then
      if (spec%temperature_ev < 0.0d0) error stop 'temperature_ev must be >= 0.'
    else if (spec%has_temperature_k) then
      if (spec%temperature_k < 0.0d0) error stop 'temperature_k must be >= 0.'
    else
      if (spec%temperature_k < 0.0d0) error stop 'temperature_k must be >= 0.'
    end if
    if (spec%m_particle <= 0.0d0) then
      error stop 'm_particle must be > 0.'
    end if

    call resolve_inject_face(cfg%sim%box_min, cfg%sim%box_max, spec%inject_face, axis, boundary_value)
    axis_t1 = modulo(axis, 3) + 1
    axis_t2 = modulo(axis + 1, 3) + 1
    if (abs(spec%pos_low(axis) - boundary_value) > 1.0d-12 .or. abs(spec%pos_high(axis) - boundary_value) > 1.0d-12) then
      error stop 'reservoir_face pos_low/pos_high must lie on the selected box face.'
    end if
    if (spec%pos_high(axis_t1) < spec%pos_low(axis_t1) .or. spec%pos_high(axis_t2) < spec%pos_low(axis_t2)) then
      error stop 'reservoir_face tangential bounds must satisfy pos_high >= pos_low.'
    end if
    if ((spec%pos_high(axis_t1) - spec%pos_low(axis_t1)) <= 0.0d0 .or. &
        (spec%pos_high(axis_t2) - spec%pos_low(axis_t2)) <= 0.0d0) then
      error stop 'reservoir_face opening area must be positive.'
    end if
    area = compute_face_area_from_bounds(spec%inject_face, spec%pos_low, spec%pos_high)
    if (area <= 0.0d0) then
      error stop 'reservoir_face opening area must be positive.'
    end if

    if (spec%has_target_macro_particles_per_batch) then
      if (spec%target_macro_particles_per_batch == -1_i32) then
        w_particle = cfg%particle_species(1)%w_particle
      else
        number_density_m3 = species_number_density_m3(spec)
        temperature_k = species_temperature_k(spec)
        call resolve_inward_normal(spec%inject_face, inward_normal)
        gamma_in = compute_inflow_flux_from_drifting_maxwellian( &
          number_density_m3, temperature_k, spec%m_particle, spec%drift_velocity, inward_normal &
        )
        w_particle = gamma_in * area * cfg%sim%batch_duration / real(spec%target_macro_particles_per_batch, dp)
      end if
      if (.not. ieee_is_finite(w_particle) .or. w_particle <= 0.0d0) then
        error stop 'target_macro_particles_per_batch produced invalid w_particle.'
      end if
      spec%w_particle = w_particle
      spec%has_w_particle = .true.
    end if

    cfg%particle_species(species_idx) = spec
  end procedure validate_reservoir_species

  !> `photo_raycast` 粒子種の必須項目と幾何/方向条件を検証する。
  module procedure validate_photo_raycast_species
    integer :: axis, axis_t1, axis_t2
    real(dp) :: boundary_value, area, direction_norm, inward_dot
    real(dp) :: inward_normal(3)
    type(particle_species_spec) :: spec

    spec = cfg%particle_species(species_idx)

    if (spec%has_npcls_per_step) then
      error stop 'particles.species.npcls_per_step is not allowed for photo_raycast.'
    end if
    if (spec%has_number_density_cm3 .or. spec%has_number_density_m3) then
      error stop 'number_density_cm3/number_density_m3 are not allowed for photo_raycast.'
    end if
    if (spec%has_w_particle) then
      error stop 'w_particle is not allowed for photo_raycast.'
    end if
    if (spec%has_target_macro_particles_per_batch) then
      error stop 'target_macro_particles_per_batch is not allowed for photo_raycast.'
    end if
    if (.not. cfg%sim%use_box) then
      error stop 'particles.species.source_mode="photo_raycast" requires sim.use_box = true.'
    end if
    if (cfg%sim%batch_duration <= 0.0d0) then
      error stop 'sim.batch_duration must be > 0 for photo_raycast.'
    end if
    if (.not. ieee_is_finite(spec%emit_current_density_a_m2) .or. spec%emit_current_density_a_m2 <= 0.0d0) then
      error stop 'photo_raycast requires emit_current_density_a_m2 > 0.'
    end if
    if (spec%rays_per_batch <= 0_i32) then
      error stop 'photo_raycast requires rays_per_batch > 0.'
    end if
    if (.not. ieee_is_finite(spec%normal_drift_speed)) then
      error stop 'normal_drift_speed must be finite.'
    end if
    if (spec%has_temperature_ev .and. spec%has_temperature_k) then
      error stop 'Specify either temperature_ev or temperature_k, not both.'
    end if
    if (spec%has_temperature_ev) then
      if (.not. ieee_is_finite(spec%temperature_ev) .or. spec%temperature_ev < 0.0d0) then
        error stop 'temperature_ev must be finite and >= 0.'
      end if
    else
      if (.not. ieee_is_finite(spec%temperature_k) .or. spec%temperature_k < 0.0d0) then
        error stop 'temperature_k must be finite and >= 0.'
      end if
    end if
    if (.not. ieee_is_finite(spec%m_particle) .or. spec%m_particle <= 0.0d0) then
      error stop 'm_particle must be finite and > 0.'
    end if
    if (.not. ieee_is_finite(spec%q_particle) .or. abs(spec%q_particle) <= 0.0d0) then
      error stop 'q_particle must be finite and non-zero for photo_raycast.'
    end if

    call resolve_inject_face(cfg%sim%box_min, cfg%sim%box_max, spec%inject_face, axis, boundary_value)
    axis_t1 = modulo(axis, 3) + 1
    axis_t2 = modulo(axis + 1, 3) + 1
    if (abs(spec%pos_low(axis) - boundary_value) > 1.0d-12 .or. abs(spec%pos_high(axis) - boundary_value) > 1.0d-12) then
      error stop 'photo_raycast pos_low/pos_high must lie on the selected box face.'
    end if
    if (spec%pos_high(axis_t1) < spec%pos_low(axis_t1) .or. spec%pos_high(axis_t2) < spec%pos_low(axis_t2)) then
      error stop 'photo_raycast tangential bounds must satisfy pos_high >= pos_low.'
    end if
    if ((spec%pos_high(axis_t1) - spec%pos_low(axis_t1)) <= 0.0d0 .or. &
        (spec%pos_high(axis_t2) - spec%pos_low(axis_t2)) <= 0.0d0) then
      error stop 'photo_raycast opening area must be positive.'
    end if
    area = compute_face_area_from_bounds(spec%inject_face, spec%pos_low, spec%pos_high)
    if (.not. ieee_is_finite(area) .or. area <= 0.0d0) then
      error stop 'photo_raycast opening area must be positive.'
    end if

    call resolve_inward_normal(spec%inject_face, inward_normal)
    if (spec%has_ray_direction) then
      if (.not. all(ieee_is_finite(spec%ray_direction))) then
        error stop 'ray_direction must be finite.'
      end if
      direction_norm = sqrt(sum(spec%ray_direction * spec%ray_direction))
      if (direction_norm <= 0.0d0) then
        error stop 'ray_direction norm must be > 0.'
      end if
      spec%ray_direction = spec%ray_direction / direction_norm
    else
      spec%ray_direction = inward_normal
    end if
    inward_dot = dot_product(spec%ray_direction, inward_normal)
    if (.not. ieee_is_finite(inward_dot) .or. inward_dot <= 0.0d0) then
      error stop 'ray_direction must point inward from inject_face.'
    end if

    cfg%particle_species(species_idx) = spec
  end procedure validate_photo_raycast_species

  !> 注入面識別子から法線軸と対応境界座標を返す。
  module procedure resolve_inject_face
    select case (trim(lower(inject_face)))
    case ('x_low')
      axis = 1
      boundary_value = box_min(1)
    case ('x_high')
      axis = 1
      boundary_value = box_max(1)
    case ('y_low')
      axis = 2
      boundary_value = box_min(2)
    case ('y_high')
      axis = 2
      boundary_value = box_max(2)
    case ('z_low')
      axis = 3
      boundary_value = box_min(3)
    case ('z_high')
      axis = 3
      boundary_value = box_max(3)
    case default
      error stop 'Unknown particles.species.inject_face.'
    end select
  end procedure resolve_inject_face

  !> 注入面識別子から内向き法線ベクトルを返す。
  module procedure resolve_inward_normal
    inward_normal = 0.0d0
    select case (trim(lower(inject_face)))
    case ('x_low')
      inward_normal(1) = 1.0d0
    case ('x_high')
      inward_normal(1) = -1.0d0
    case ('y_low')
      inward_normal(2) = 1.0d0
    case ('y_high')
      inward_normal(2) = -1.0d0
    case ('z_low')
      inward_normal(3) = 1.0d0
    case ('z_high')
      inward_normal(3) = -1.0d0
    case default
      error stop 'Unknown particles.species.inject_face.'
    end select
  end procedure resolve_inward_normal

  !> 粒子種設定から有効粒子数密度 `[1/m^3]` を計算する。
  module procedure species_number_density_m3
    number_density_m3 = spec%number_density_m3
    if (spec%has_number_density_cm3) number_density_m3 = spec%number_density_cm3 * 1.0d6
  end procedure species_number_density_m3

  !> 粒子種設定から有効温度 `[K]` を計算する。
  module procedure species_temperature_k
    temperature_k = spec%temperature_k
    if (spec%has_temperature_ev) temperature_k = spec%temperature_ev * 1.160451812d4
  end procedure species_temperature_k

  !> drifting Maxwellian の片側流入束 `[1/m^2/s]` を評価する。
  module procedure compute_inflow_flux_from_drifting_maxwellian
    real(dp) :: sigma, alpha, u_n

    u_n = dot_product(drift_velocity, inward_normal)
    sigma = sqrt(k_boltzmann * temperature_k / m_particle)
    if (sigma <= 0.0d0) then
      gamma_in = number_density_m3 * max(0.0d0, u_n)
      return
    end if

    alpha = u_n / sigma
    gamma_in = number_density_m3 * (sigma * standard_normal_pdf(alpha) + u_n * standard_normal_cdf(alpha))
  end procedure compute_inflow_flux_from_drifting_maxwellian

  !> 標準正規分布の PDF を評価する。
  module procedure standard_normal_pdf
    real(dp), parameter :: inv_sqrt_2pi = 3.98942280401432678d-1

    pdf = inv_sqrt_2pi * exp(-0.5d0 * x * x)
  end procedure standard_normal_pdf

  !> 標準正規分布の CDF を評価する。
  module procedure standard_normal_cdf
    real(dp), parameter :: inv_sqrt_2 = 7.07106781186547524d-1

    cdf = 0.5d0 * (1.0d0 + erf(x * inv_sqrt_2))
  end procedure standard_normal_cdf

  !> 注入面上開口矩形の有効面積 `[m^2]` を計算する。
  module procedure compute_face_area_from_bounds
    integer :: axis_t1, axis_t2

    call resolve_face_axes(inject_face, axis_t1, axis_t2)
    area = (pos_high(axis_t1) - pos_low(axis_t1)) * (pos_high(axis_t2) - pos_low(axis_t2))
  end procedure compute_face_area_from_bounds

  !> 注入面識別子から接線2軸インデックスを返す。
  module procedure resolve_face_axes
    select case (trim(lower(inject_face)))
    case ('x_low', 'x_high')
      axis_t1 = 2
      axis_t2 = 3
    case ('y_low', 'y_high')
      axis_t1 = 3
      axis_t2 = 1
    case ('z_low', 'z_high')
      axis_t1 = 1
      axis_t2 = 2
    case default
      error stop 'Unknown particles.species.inject_face.'
    end select
  end procedure resolve_face_axes

end submodule bem_app_config_parser_validate
