!> 場・periodic2・panel の型付き設定と旧 `[sim]` 設定の正規化を定義する。
module bem_physics_config_types
  use, intrinsic :: ieee_arithmetic, only: ieee_is_finite
  use bem_kinds, only: dp, i32
  use bem_types, only: sim_config, bc_periodic
  use bem_string_utils, only: lower_ascii
  implicit none
  private

  integer(i32), parameter, public :: physics_config_ok = 0_i32
  integer(i32), parameter, public :: physics_config_invalid_combination = 1_i32
  integer(i32), parameter, public :: physics_config_unavailable = 2_i32

  type, public :: field_physics_config
    character(len=32) :: backend = 'auto'
    character(len=32) :: normalization = 'si'
  end type field_physics_config

  type, public :: periodic2_physics_config
    character(len=32) :: nonzero_mode_backend = 'not_applicable'
    character(len=32) :: zero_mode_policy = 'not_applicable'
    character(len=32) :: lower_boundary_model = 'not_applicable'
    integer(i32) :: reference_mode_layers = 4_i32
    integer(i32) :: panel_quadrature_order = 12_i32
    real(dp) :: max_nonzero_mode_potential_step = 0.0_dp
  end type periodic2_physics_config

  type, public :: panel_kernel_config
    character(len=32) :: kernel_id = 'triangle_p0_exact_auto'
    character(len=32) :: surface_side_policy = 'per_element'
  end type panel_kernel_config

  public :: normalize_legacy_physics_config
  public :: derive_field_panel_config
  public :: validate_phase1_panel_config
  public :: validate_active_physics_config

contains

  !> 現行 `sim_config` を型付き場設定へ写像する。
  subroutine normalize_legacy_physics_config(sim, field, periodic2, panel)
    type(sim_config), intent(in) :: sim
    type(field_physics_config), intent(out) :: field
    type(periodic2_physics_config), intent(out) :: periodic2
    type(panel_kernel_config), intent(out) :: panel
    character(len=32) :: bc_mode, far_mode

    call derive_field_panel_config(sim, field, panel)
    periodic2 = periodic2_physics_config()

    bc_mode = lower_ascii(trim(sim%field_bc_mode))
    if (trim(bc_mode) /= 'periodic2') return

    far_mode = lower_ascii(trim(sim%field_periodic_far_correction))
    select case (trim(far_mode))
    case ('none', 'auto')
      periodic2%nonzero_mode_backend = 'legacy_finite_images'
      periodic2%zero_mode_policy = 'legacy_not_decomposed'
      periodic2%lower_boundary_model = 'legacy_implicit'
    case ('cached_kneq0')
      periodic2%nonzero_mode_backend = 'cached_kneq0'
      periodic2%zero_mode_policy = 'exclude_k0'
      periodic2%lower_boundary_model = 'e_bottom_zero'
    case default
      periodic2%nonzero_mode_backend = 'invalid'
      periodic2%zero_mode_policy = 'invalid'
      periodic2%lower_boundary_model = 'invalid'
    end select
  end subroutine normalize_legacy_physics_config

  !> `[sim]` だけから field/panel の派生契約を構築する。
  pure subroutine derive_field_panel_config(sim, field, panel)
    type(sim_config), intent(in) :: sim
    type(field_physics_config), intent(out) :: field
    type(panel_kernel_config), intent(out) :: panel

    field = field_physics_config()
    panel = panel_kernel_config()
    field%backend = lower_ascii(trim(sim%field_solver))
    field%normalization = lower_ascii(trim(sim%field_normalization))
    select case (trim(field%backend))
    case ('direct')
      panel%kernel_id = 'triangle_p0_exact_direct'
    case ('treecode')
      panel%kernel_id = 'triangle_p0_exact_tree_near'
    case ('fmm')
      panel%kernel_id = 'triangle_p0_exact_p2m_near'
    case default
      panel%kernel_id = 'triangle_p0_exact_auto'
    end select
  end subroutine derive_field_panel_config

  !> triangle P0 direct/treecode/FMM kernel の solver/boundary 契約を検証する。
  subroutine validate_phase1_panel_config(sim, panel, status, message)
    type(sim_config), intent(in) :: sim
    type(panel_kernel_config), intent(in) :: panel
    integer(i32), intent(out) :: status
    character(len=*), intent(out) :: message
    character(len=32) :: kernel_id, solver, boundary

    status = physics_config_ok
    message = ''
    kernel_id = lower_ascii(trim(panel%kernel_id))
    solver = lower_ascii(trim(sim%field_solver))
    boundary = lower_ascii(trim(sim%field_bc_mode))
    select case (trim(solver))
    case ('direct')
      if (trim(kernel_id) /= 'triangle_p0_exact_direct' .or. trim(boundary) /= 'free') then
        call reject(physics_config_unavailable, 'triangle_p0 direct requires its exact free-space kernel.', status, message)
      end if
    case ('treecode')
      if (trim(kernel_id) /= 'triangle_p0_exact_tree_near' .or. trim(boundary) /= 'free') then
        call reject( &
          physics_config_invalid_combination, &
          'triangle_p0 treecode requires exact panel-near/monopole-far free-space kernel.', status, message &
          )
      end if
    case ('fmm')
      if (trim(kernel_id) /= 'triangle_p0_exact_p2m_near') then
        call reject( &
          physics_config_invalid_combination, &
          'triangle_p0 FMM requires exact P2M/panel-near kernel.', status, message &
          )
      else if (trim(boundary) /= 'free' .and. trim(boundary) /= 'periodic2') then
        call reject( &
          physics_config_invalid_combination, &
          'triangle_p0 FMM supports free or periodic2 boundaries.', status, message &
          )
      end if
    case ('auto')
      if (trim(kernel_id) /= 'triangle_p0_exact_auto' .or. trim(boundary) /= 'free') then
        call reject( &
          physics_config_invalid_combination, &
          'triangle_p0 auto requires its free-space auto kernel.', status, message &
          )
      end if
    case default
      call reject(physics_config_unavailable, 'triangle_p0 supports direct, treecode, FMM, or auto solving.', status, message)
    end select
  end subroutine validate_phase1_panel_config

  !> 有限画像、panel reference、cached k!=0 の periodic2 構成を検証する。
  subroutine validate_active_physics_config(sim, field, periodic2, panel, status, message)
    type(sim_config), intent(in) :: sim
    type(field_physics_config), intent(in) :: field
    type(periodic2_physics_config), intent(in) :: periodic2
    type(panel_kernel_config), intent(in) :: panel
    integer(i32), intent(out) :: status
    character(len=*), intent(out) :: message
    character(len=32) :: nonzero_backend

    status = physics_config_ok
    message = ''
    nonzero_backend = lower_ascii(trim(periodic2%nonzero_mode_backend))

    if (.not. ieee_is_finite(periodic2%max_nonzero_mode_potential_step) .or. &
        periodic2%max_nonzero_mode_potential_step < 0.0_dp) then
      call reject(physics_config_invalid_combination, &
                  'periodic2.max_nonzero_mode_potential_step must be finite and >= 0.', status, message)
      return
    end if

    if (trim(lower_ascii(sim%multiple_box_events_retry_backend)) == 'upper_panel_fourier' .and. &
        trim(nonzero_backend) /= 'cached_kneq0') then
      call reject( &
        physics_config_invalid_combination, &
        'upper_panel_fourier retry requires the cached_kneq0 periodic backend.', status, message &
        )
      return
    end if

    if (trim(nonzero_backend) == 'not_applicable') then
      call validate_phase1_panel_config(sim, panel, status, message)
      return
    end if

    if (trim(lower_ascii(sim%field_bc_mode)) /= 'periodic2' .or. .not. sim%use_box) then
      call reject( &
        physics_config_invalid_combination, &
        'Periodic physics requires periodic2 box geometry.', status, message &
        )
      return
    end if
    if (any(sim%bc_low(1:2) /= bc_periodic) .or. any(sim%bc_high(1:2) /= bc_periodic) .or. &
        sim%bc_low(3) == bc_periodic .or. sim%bc_high(3) == bc_periodic) then
      call reject(physics_config_unavailable, 'Periodic2 requires x/y periodic and z nonperiodic.', status, message)
      return
    end if

    if (trim(nonzero_backend) == 'legacy_finite_images') then
      if (trim(lower_ascii(periodic2%zero_mode_policy)) /= 'legacy_not_decomposed') then
        call reject( &
          physics_config_invalid_combination, &
          'legacy_finite_images requires zero_mode_policy=legacy_not_decomposed.', status, message &
          )
        return
      end if
      call validate_phase1_panel_config(sim, panel, status, message)
      return
    end if

    if (trim(nonzero_backend) == 'panel_spectral_reference') then
      if (trim(lower_ascii(field%backend)) /= 'direct' .or. trim(lower_ascii(sim%field_solver)) /= 'direct') then
        call reject( &
          physics_config_invalid_combination, &
          'panel_spectral_reference requires field backend direct.', status, message &
          )
        return
      end if
      if (trim(lower_ascii(periodic2%zero_mode_policy)) /= 'exclude_k0' .or. &
          .not. supported_lower_boundary(periodic2%lower_boundary_model)) then
        call reject( &
          physics_config_invalid_combination, &
          'panel_spectral_reference requires exclude_k0 and a supported lower boundary model.', &
          status, message &
          )
        return
      end if
      if (trim(lower_ascii(panel%kernel_id)) /= 'triangle_p0_exact_direct') then
        call reject( &
          physics_config_invalid_combination, &
          'panel_spectral_reference requires triangle_p0_exact_direct.', status, message &
          )
        return
      end if
      if (periodic2%reference_mode_layers < 1_i32 .or. periodic2%panel_quadrature_order < 2_i32) then
        call reject( &
          physics_config_invalid_combination, &
          'Periodic reference orders are out of range.', status, message &
          )
        return
      end if
      if (.not. ieee_is_finite(periodic2%max_nonzero_mode_potential_step) .or. &
          periodic2%max_nonzero_mode_potential_step /= 0.0_dp) then
        call reject( &
          physics_config_invalid_combination, &
          'panel_spectral_reference does not support max_nonzero_mode_potential_step.', status, message &
          )
      end if
      return
    end if

    if (trim(nonzero_backend) /= 'cached_kneq0') then
      call reject(physics_config_invalid_combination, 'Unknown periodic nonzero-mode backend.', status, message)
      return
    end if
    if (trim(lower_ascii(field%backend)) /= 'fmm' .or. trim(lower_ascii(sim%field_solver)) /= 'fmm') then
      call reject( &
        physics_config_invalid_combination, &
        'cached_kneq0 requires the periodic2 FMM backend.', status, message &
        )
      return
    end if
    if (trim(lower_ascii(periodic2%zero_mode_policy)) /= 'exclude_k0' .or. &
        .not. supported_lower_boundary(periodic2%lower_boundary_model)) then
      call reject( &
        physics_config_invalid_combination, &
        'cached_kneq0 requires exclude_k0 and a supported lower boundary model.', status, message &
        )
      return
    end if
    if (trim(lower_ascii(panel%kernel_id)) /= 'triangle_p0_exact_p2m_near') then
      call reject( &
        physics_config_invalid_combination, &
        'cached_kneq0 requires the exact FMM panel kernel.', status, message &
        )
      return
    end if
  end subroutine validate_active_physics_config

  pure logical function supported_lower_boundary(model) result(supported)
    character(len=*), intent(in) :: model
    character(len=32) :: normalized

    normalized = lower_ascii(trim(model))
    supported = trim(normalized) == 'e_bottom_zero' .or. trim(normalized) == 'symmetric_vacuum'
  end function supported_lower_boundary

  pure subroutine reject(code, text, status, message)
    integer(i32), intent(in) :: code
    character(len=*), intent(in) :: text
    integer(i32), intent(out) :: status
    character(len=*), intent(out) :: message

    status = code
    message = text
  end subroutine reject

end module bem_physics_config_types
