use runmat_analysis_core::{AnalysisField, AnalysisFieldValues, AnalysisModel};
use runmat_analysis_fea::diagnostics::FeaDiagnostic;
use runmat_analysis_fea::{ComputeBackend, FeaRunResult};
use runmat_geometry_core::GeometryAsset;
use runmat_meshing_core::RegionMeshMapping;
use serde::{Deserialize, Serialize};

fn default_prep_coordinate_span_m() -> f64 {
    1.0
}

fn default_prep_coordinate_secondary_span_m() -> f64 {
    0.0
}

fn default_prep_coordinate_active_dimension_count() -> usize {
    1
}

fn default_prep_coordinate_characteristic_length_m() -> f64 {
    1.0
}

fn default_zero_usize() -> usize {
    0
}

fn default_zero_f64() -> f64 {
    0.0
}

fn default_reference_element_coordinates_m() -> [[f64; 3]; 3] {
    [[0.0; 3]; 3]
}

fn default_element_topology_sample_edge_nodes() -> [[u32; 2]; 8] {
    [[0; 2]; 8]
}

fn default_element_topology_sample_node_coordinates_m() -> [[f64; 3]; 8] {
    [[0.0; 3]; 8]
}

fn default_element_topology_sample_element_edges() -> [[u32; 3]; 4] {
    [[0; 3]; 4]
}

fn default_element_topology_sample_element_orientations() -> [[i8; 3]; 4] {
    [[0; 3]; 4]
}

fn default_element_topology_sample_element_areas_m2() -> [f64; 4] {
    [0.0; 4]
}

fn default_element_topology_node_coordinates_m() -> Vec<[f64; 3]> {
    Vec::new()
}

fn default_element_topology_edge_nodes() -> Vec<[u32; 2]> {
    Vec::new()
}

fn default_element_topology_element_edges() -> Vec<[u32; 3]> {
    Vec::new()
}

fn default_element_topology_element_orientations() -> Vec<[i8; 3]> {
    Vec::new()
}

fn default_element_topology_element_areas_m2() -> Vec<f64> {
    Vec::new()
}

#[derive(Debug, Clone, PartialEq)]
pub struct AnalysisValidateResult {
    pub valid: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisCreateModelIntentSpec {
    pub model_id: String,
    pub profile: AnalysisCreateModelProfile,
    #[serde(default)]
    pub prep_context: Option<AnalysisCreateModelPrepContext>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisCreateModelPrepContext {
    pub source_geometry_id: String,
    pub source_geometry_revision: u32,
    pub region_mappings: Vec<RegionMeshMapping>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisRunPrepContext {
    pub prepared_mesh_count: usize,
    pub prepared_node_count: usize,
    pub prepared_element_count: usize,
    pub mapped_region_count: usize,
    pub min_scaled_jacobian: f64,
    pub mean_aspect_ratio: f64,
    pub inverted_element_count: usize,
    pub mapped_load_count: usize,
    pub mapped_bc_count: usize,
    pub layout_seed: u64,
    pub topology_dof_multiplier: f64,
    pub topology_bandwidth_estimate: u32,
    pub mapped_region_participation_ratio: f64,
    pub topology_surface_patch_ratio: f64,
    pub topology_volume_core_ratio: f64,
    pub topology_mixed_family_ratio: f64,
    pub topology_region_span_mean: f64,
    pub topology_region_block_count: usize,
    pub topology_region_mesh_mean: f64,
    pub topology_region_mesh_variance: f64,
    pub topology_triangle_family_ratio: f64,
    pub topology_quad_family_ratio: f64,
    pub topology_tet_family_ratio: f64,
    pub topology_hex_family_ratio: f64,
    #[serde(default = "default_prep_coordinate_span_m")]
    pub coordinate_span_x_m: f64,
    #[serde(default = "default_prep_coordinate_secondary_span_m")]
    pub coordinate_span_y_m: f64,
    #[serde(default = "default_prep_coordinate_secondary_span_m")]
    pub coordinate_span_z_m: f64,
    #[serde(default = "default_prep_coordinate_active_dimension_count")]
    pub coordinate_active_dimension_count: usize,
    #[serde(default = "default_prep_coordinate_characteristic_length_m")]
    pub coordinate_characteristic_length_m: f64,
    #[serde(default = "default_zero_usize")]
    pub element_geometry_node_count: usize,
    #[serde(default = "default_zero_usize")]
    pub element_geometry_edge_count: usize,
    #[serde(default = "default_zero_f64")]
    pub mean_element_edge_length_m: f64,
    #[serde(default = "default_zero_f64")]
    pub mean_element_area_m2: f64,
    #[serde(default = "default_zero_f64")]
    pub element_geometry_coverage_ratio: f64,
    #[serde(default = "default_reference_element_coordinates_m")]
    pub reference_element_coordinates_m: [[f64; 3]; 3],
    #[serde(default = "default_zero_f64")]
    pub reference_element_area_m2: f64,
    #[serde(default = "default_zero_usize")]
    pub control_volume_cell_count: usize,
    #[serde(default = "default_zero_usize")]
    pub control_volume_face_count: usize,
    #[serde(default = "default_zero_usize")]
    pub control_volume_internal_face_count: usize,
    #[serde(default = "default_zero_usize")]
    pub control_volume_boundary_face_count: usize,
    #[serde(default = "default_zero_f64")]
    pub control_volume_connectivity_coverage_ratio: f64,
    #[serde(default = "default_zero_usize")]
    pub element_topology_sample_element_count: usize,
    #[serde(default = "default_zero_usize")]
    pub element_topology_sample_edge_count: usize,
    #[serde(default = "default_element_topology_sample_edge_nodes")]
    pub element_topology_sample_edge_nodes: [[u32; 2]; 8],
    #[serde(default = "default_element_topology_sample_node_coordinates_m")]
    pub element_topology_sample_node_coordinates_m: [[f64; 3]; 8],
    #[serde(default = "default_element_topology_sample_element_edges")]
    pub element_topology_sample_element_edges: [[u32; 3]; 4],
    #[serde(default = "default_element_topology_sample_element_orientations")]
    pub element_topology_sample_element_orientations: [[i8; 3]; 4],
    #[serde(default = "default_element_topology_sample_element_areas_m2")]
    pub element_topology_sample_element_areas_m2: [f64; 4],
    #[serde(default = "default_element_topology_node_coordinates_m")]
    pub element_topology_node_coordinates_m: Vec<[f64; 3]>,
    #[serde(default = "default_element_topology_edge_nodes")]
    pub element_topology_edge_nodes: Vec<[u32; 2]>,
    #[serde(default = "default_element_topology_element_edges")]
    pub element_topology_element_edges: Vec<[u32; 3]>,
    #[serde(default = "default_element_topology_element_orientations")]
    pub element_topology_element_orientations: Vec<[i8; 3]>,
    #[serde(default = "default_element_topology_element_areas_m2")]
    pub element_topology_element_areas_m2: Vec<f64>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnalysisCreateModelProfile {
    LinearStaticStructural,
    ThermoMechanicalCoupled,
    ThermalStandalone,
    ModalStructural,
    AcousticHarmonic,
    TransientStructural,
    NonlinearStructural,
    ElectromagneticStatic,
    CfdSteadyState,
    CfdTransient,
    ChtCoupled,
    FsiCoupled,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PrecisionMode {
    Fp32,
    Fp64,
    Mixed,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PreconditionerMode {
    Auto,
    Jacobi,
    Amg,
    Ilu,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QualityPolicy {
    Strict,
    Balanced,
    Exploratory,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PrepCalibrationProfile {
    Auto,
    Fast,
    Balanced,
    Conservative,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ThermoRegionTemperatureDelta {
    pub region_id: String,
    pub temperature_delta_k: f64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ThermoTimeProfilePoint {
    pub normalized_time: f64,
    pub scale: f64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ThermoFieldInterpolationMode {
    Linear,
    Step,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ThermoFieldSource {
    pub source_id: String,
    pub revision: u32,
    #[serde(default)]
    pub interpolation_mode: Option<ThermoFieldInterpolationMode>,
    #[serde(default)]
    pub expected_region_ids: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ThermoMechanicalCouplingOptions {
    pub enabled: bool,
    pub reference_temperature_k: f64,
    pub applied_temperature_delta_k: f64,
    pub thermal_expansion_coefficient: f64,
    #[serde(default)]
    pub field_artifact_id: Option<String>,
    #[serde(default)]
    pub field_source: Option<ThermoFieldSource>,
    #[serde(default)]
    pub region_temperature_deltas: Vec<ThermoRegionTemperatureDelta>,
    #[serde(default)]
    pub time_profile: Vec<ThermoTimeProfilePoint>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ElectroRegionConductivityScale {
    pub region_id: String,
    pub conductivity_scale: f64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ElectroTimeProfilePoint {
    pub normalized_time: f64,
    pub current_scale: f64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ElectroThermalCouplingOptions {
    pub enabled: bool,
    pub reference_temperature_k: f64,
    pub applied_voltage_v: f64,
    pub base_electrical_conductivity_s_per_m: f64,
    pub resistive_heating_coefficient: f64,
    #[serde(default)]
    pub region_conductivity_scales: Vec<ElectroRegionConductivityScale>,
    #[serde(default)]
    pub time_profile: Vec<ElectroTimeProfilePoint>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PlasticityConstitutiveOptions {
    pub enabled: bool,
    pub yield_strain: f64,
    pub hardening_modulus_ratio: f64,
    pub saturation_exponent: f64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ContactInterfaceOptions {
    pub enabled: bool,
    pub penalty_stiffness_scale: f64,
    pub max_penetration_ratio: f64,
    pub friction_coefficient: f64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QualityReasonCode {
    MaterialAssignmentConflict,
    SolverNotConverged,
    SolverBackendFallback,
    FieldPromotionFallback,
    ModalResidualExceeded,
    ModalOrthogonalityExceeded,
    ModalSeparationLow,
    TransientResidualExceeded,
    TransientStabilityExceeded,
    TransientStepFailure,
    ThermoMechanicalTransientStress,
    ThermoMechanicalConstitutiveSpreadHigh,
    ThermoMechanicalAssignmentHeterogeneityHigh,
    ThermoMechanicalGradientInstability,
    ThermoMechanicalFieldCoverageLow,
    ThermoMechanicalFieldExtrapolationHigh,
    ElectroThermalTransientStress,
    ElectroThermalNonlinearStress,
    ElectromagneticSolveQualityLow,
    ElectromagneticConductivitySpreadHigh,
    ElectromagneticMaterialHeterogeneityHigh,
    ElectromagneticAssignmentCoverageLow,
    ElectromagneticRegionContrastHigh,
    ElectromagneticConditioningHigh,
    ElectromagneticSourceRealizationLow,
    ElectromagneticSourceRegionCoverageLow,
    ElectromagneticSourceMaterialAlignmentLow,
    ElectromagneticSourceOverlapHigh,
    ElectromagneticSourceInterferenceHigh,
    ElectromagneticBoundaryLocalizationLow,
    ElectromagneticGroundAnchorEffectivenessLow,
    ElectromagneticInsulationLeakageHigh,
    ElectromagneticBoundaryAnchoringLow,
    ElectromagneticFluxDivergenceHigh,
    ElectromagneticEnergyImbalanceHigh,
    ElectromagneticBoundaryEnergyLow,
    ElectromagneticBoundaryPenaltyConditioningHigh,
    ElectromagneticSourceRegionEnergyConsistencyLow,
    ElectromagneticRealResidualHigh,
    ElectromagneticImagResidualHigh,
    ElectromagneticSweepCoverageLow,
    ElectromagneticResonanceSharpnessLow,
    PlasticityNonlinearStress,
    ContactNonlinearStress,
    NonlinearResidualExceeded,
    NonlinearIncrementFailure,
    ThermoMechanicalNonlinearStress,
    ThermalResidualExceeded,
    ThermalConstitutiveSpreadHigh,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct QualityReason {
    pub code: QualityReasonCode,
    pub detail: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub preconditioner_mode: PreconditionerMode,
    pub quality_policy: QualityPolicy,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisElectromagneticRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub quality_policy: QualityPolicy,
    pub residual_target: f64,
    pub harmonic_tolerance: f64,
    pub harmonic_max_iterations: usize,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
    #[serde(default)]
    pub sweep_enabled: bool,
    #[serde(default)]
    pub sweep_frequency_hz: Vec<f64>,
}

impl Default for AnalysisElectromagneticRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            residual_target: 1.0e-6,
            harmonic_tolerance: 1.0e-7,
            harmonic_max_iterations: 96,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
            sweep_enabled: false,
            sweep_frequency_hz: Vec::new(),
        }
    }
}

impl Default for AnalysisRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            preconditioner_mode: PreconditionerMode::Auto,
            quality_policy: QualityPolicy::Balanced,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisTransientRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub quality_policy: QualityPolicy,
    pub time_step_s: f64,
    pub min_time_step_s: f64,
    pub max_time_step_s: f64,
    pub step_count: usize,
    pub max_linear_iters: usize,
    pub tolerance: f64,
    pub residual_target: f64,
    pub adaptive_time_step: bool,
    pub max_step_retries: usize,
    pub adapt_min_scale: f64,
    pub adapt_max_scale: f64,
    pub adapt_growth_exponent: f64,
    pub adapt_retry_growth_cap: f64,
    pub adapt_nonconverged_shrink: f64,
    pub dt_bucket_rel_tolerance: f64,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
}

impl Default for AnalysisTransientRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            time_step_s: 1.0e-3,
            min_time_step_s: 1.0e-6,
            max_time_step_s: 2.0e-2,
            step_count: 10,
            max_linear_iters: 128,
            tolerance: 1.0e-8,
            residual_target: 1.0e-6,
            adaptive_time_step: true,
            max_step_retries: 4,
            adapt_min_scale: 0.8,
            adapt_max_scale: 1.25,
            adapt_growth_exponent: 0.35,
            adapt_retry_growth_cap: 1.05,
            adapt_nonconverged_shrink: 0.75,
            dt_bucket_rel_tolerance: 0.0,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisAcousticRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub quality_policy: QualityPolicy,
    pub mode_count: usize,
    pub residual_warn_threshold: f64,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
}

impl Default for AnalysisAcousticRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            mode_count: 3,
            residual_warn_threshold: 1.0e-3,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisCfdRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub quality_policy: QualityPolicy,
    pub time_step_s: f64,
    pub step_count: usize,
    pub max_linear_iters: usize,
    pub tolerance: f64,
    pub residual_warn_threshold: f64,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
}

impl Default for AnalysisCfdRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            time_step_s: 1.0e-3,
            step_count: 12,
            max_linear_iters: 128,
            tolerance: 1.0e-8,
            residual_warn_threshold: 1.0e-5,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisChtRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub quality_policy: QualityPolicy,
    pub time_step_s: f64,
    pub step_count: usize,
    pub max_linear_iters: usize,
    pub tolerance: f64,
    pub residual_warn_threshold: f64,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
}

impl Default for AnalysisChtRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            time_step_s: 1.0e-3,
            step_count: 12,
            max_linear_iters: 128,
            tolerance: 1.0e-8,
            residual_warn_threshold: 1.0e-4,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisFsiRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub quality_policy: QualityPolicy,
    pub time_step_s: f64,
    pub step_count: usize,
    pub max_linear_iters: usize,
    pub tolerance: f64,
    pub residual_warn_threshold: f64,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
}

impl Default for AnalysisFsiRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            time_step_s: 1.0e-3,
            step_count: 12,
            max_linear_iters: 128,
            tolerance: 1.0e-8,
            residual_warn_threshold: 1.0e-4,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisThermalRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub quality_policy: QualityPolicy,
    pub step_count: usize,
    pub time_step_s: f64,
    pub residual_warn_threshold: f64,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
}

impl Default for AnalysisThermalRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            step_count: 10,
            time_step_s: 1.0e-2,
            residual_warn_threshold: 1.0e-4,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

impl AnalysisTransientRunOptions {
    pub fn coarse() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp32,
            quality_policy: QualityPolicy::Exploratory,
            time_step_s: 5.0e-3,
            min_time_step_s: 5.0e-4,
            max_time_step_s: 2.0e-2,
            step_count: 6,
            max_linear_iters: 64,
            tolerance: 1.0e-6,
            residual_target: 1.0e-4,
            adaptive_time_step: true,
            max_step_retries: 2,
            adapt_min_scale: 0.75,
            adapt_max_scale: 1.3,
            adapt_growth_exponent: 0.3,
            adapt_retry_growth_cap: 1.02,
            adapt_nonconverged_shrink: 0.7,
            dt_bucket_rel_tolerance: 0.02,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }

    pub fn balanced() -> Self {
        Self::default()
    }

    pub fn production_recommended() -> Self {
        Self {
            quality_policy: QualityPolicy::Balanced,
            deterministic_mode: true,
            precision_mode: PrecisionMode::Fp64,
            dt_bucket_rel_tolerance: 0.01,
            ..Self::balanced()
        }
    }

    pub fn high_accuracy() -> Self {
        Self {
            deterministic_mode: true,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Strict,
            time_step_s: 5.0e-4,
            min_time_step_s: 5.0e-6,
            max_time_step_s: 2.0e-3,
            step_count: 24,
            max_linear_iters: 256,
            tolerance: 1.0e-10,
            residual_target: 1.0e-7,
            adaptive_time_step: true,
            max_step_retries: 8,
            adapt_min_scale: 0.85,
            adapt_max_scale: 1.2,
            adapt_growth_exponent: 0.45,
            adapt_retry_growth_cap: 1.03,
            adapt_nonconverged_shrink: 0.8,
            dt_bucket_rel_tolerance: 0.005,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisModalRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub quality_policy: QualityPolicy,
    pub mode_count: usize,
    pub residual_warn_threshold: f64,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisNonlinearRunOptions {
    pub deterministic_mode: bool,
    pub precision_mode: PrecisionMode,
    pub quality_policy: QualityPolicy,
    pub increment_count: usize,
    pub max_newton_iters: usize,
    pub tolerance: f64,
    pub residual_convergence_factor: f64,
    pub increment_norm_tolerance: f64,
    pub line_search: bool,
    pub max_line_search_backtracks: usize,
    pub line_search_reduction: f64,
    pub tangent_refresh_interval: usize,
    #[serde(default)]
    pub prep_context: Option<AnalysisRunPrepContext>,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    #[serde(default)]
    pub prep_calibration_profile: Option<PrepCalibrationProfile>,
}

impl Default for AnalysisNonlinearRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            increment_count: 12,
            max_newton_iters: 24,
            tolerance: 1.0e-6,
            residual_convergence_factor: 5.0,
            increment_norm_tolerance: 1.0e-7,
            line_search: true,
            max_line_search_backtracks: 6,
            line_search_reduction: 0.5,
            tangent_refresh_interval: 2,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

impl AnalysisNonlinearRunOptions {
    pub fn coarse() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp32,
            quality_policy: QualityPolicy::Exploratory,
            increment_count: 8,
            max_newton_iters: 16,
            tolerance: 5.0e-6,
            residual_convergence_factor: 8.0,
            increment_norm_tolerance: 5.0e-7,
            line_search: false,
            max_line_search_backtracks: 0,
            line_search_reduction: 0.6,
            tangent_refresh_interval: 4,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }

    pub fn balanced() -> Self {
        Self::default()
    }

    pub fn high_accuracy() -> Self {
        Self {
            deterministic_mode: true,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Strict,
            increment_count: 24,
            max_newton_iters: 40,
            tolerance: 1.0e-7,
            residual_convergence_factor: 3.0,
            increment_norm_tolerance: 5.0e-8,
            line_search: true,
            max_line_search_backtracks: 10,
            line_search_reduction: 0.5,
            tangent_refresh_interval: 1,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }

    pub fn production_recommended() -> Self {
        Self {
            deterministic_mode: true,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            increment_count: 24,
            max_newton_iters: 28,
            tolerance: 1.0e-6,
            residual_convergence_factor: 4.0,
            increment_norm_tolerance: 8.0e-8,
            line_search: true,
            max_line_search_backtracks: 8,
            line_search_reduction: 0.5,
            tangent_refresh_interval: 2,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

impl Default for AnalysisModalRunOptions {
    fn default() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Balanced,
            mode_count: 3,
            residual_warn_threshold: 1.0e-3,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

impl AnalysisModalRunOptions {
    pub fn coarse() -> Self {
        Self {
            deterministic_mode: false,
            precision_mode: PrecisionMode::Fp32,
            quality_policy: QualityPolicy::Exploratory,
            mode_count: 2,
            residual_warn_threshold: 5.0e-3,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }

    pub fn balanced() -> Self {
        Self::default()
    }

    pub fn high_accuracy() -> Self {
        Self {
            deterministic_mode: true,
            precision_mode: PrecisionMode::Fp64,
            quality_policy: QualityPolicy::Strict,
            mode_count: 8,
            residual_warn_threshold: 5.0e-4,
            prep_context: None,
            prep_artifact_id: None,
            prep_calibration_profile: None,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QualityGate {
    Pass,
    Warn,
    Fail,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RunStatus {
    Publishable,
    Degraded,
    Rejected,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RunProvenance {
    pub backend: ComputeBackend,
    pub solver_backend: String,
    pub solver_device_apply_k_ratio: f64,
    pub solver_host_sync_count: u32,
    pub precision_mode: String,
    pub deterministic_mode: bool,
    pub solver_method: String,
    pub preconditioner: String,
    pub quality_policy: String,
    pub fallback_events: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisRenderTopology {
    pub schema_version: String,
    pub source: AnalysisRenderTopologySource,
    pub meshes: Vec<AnalysisRenderMesh>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnalysisRenderTopologySource {
    SolverPrep,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisRenderMesh {
    pub mesh_id: String,
    pub vertices: Vec<[f64; 3]>,
    pub triangles: Vec<[u32; 3]>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisRunResult {
    pub run_id: String,
    pub run: FeaRunResult,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub render_topology: Option<AnalysisRenderTopology>,
    pub modal_results: Option<ModalResultsData>,
    #[serde(default)]
    pub thermal_results: Option<ThermalResultsData>,
    pub transient_results: Option<TransientResultsData>,
    pub nonlinear_results: Option<NonlinearResultsData>,
    #[serde(default)]
    pub electromagnetic_results: Option<ElectromagneticResultsData>,
    pub model_validity: QualityGate,
    pub solver_convergence: QualityGate,
    pub result_quality: QualityGate,
    pub run_status: RunStatus,
    pub publishable: bool,
    pub quality_reasons: Vec<QualityReason>,
    pub provenance: RunProvenance,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisArtifactRecord {
    pub run_id: String,
    pub created_at: String,
    pub op_version: String,
    pub field_ids: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisResultsQuery {
    pub include_fields: Vec<String>,
    pub include_field_values: bool,
    pub include_diagnostics: bool,
    pub diagnostic_codes: Vec<String>,
    pub include_modal_results: bool,
    pub mode_indices: Vec<usize>,
    pub include_transient_results: bool,
    pub transient_snapshot_indices: Vec<usize>,
    pub include_nonlinear_results: bool,
    pub include_electromagnetic_results: bool,
}

impl Default for AnalysisResultsQuery {
    fn default() -> Self {
        Self {
            include_fields: Vec::new(),
            include_field_values: true,
            include_diagnostics: true,
            diagnostic_codes: Vec::new(),
            include_modal_results: true,
            mode_indices: Vec::new(),
            include_transient_results: true,
            transient_snapshot_indices: Vec::new(),
            include_nonlinear_results: true,
            include_electromagnetic_results: true,
        }
    }
}

impl AnalysisResultsQuery {
    pub fn metadata_only() -> Self {
        Self {
            include_field_values: false,
            include_modal_results: false,
            include_transient_results: false,
            include_nonlinear_results: false,
            include_electromagnetic_results: false,
            ..Self::default()
        }
    }

    pub fn field_values(field_id: impl Into<String>) -> Self {
        Self {
            include_fields: vec![field_id.into()],
            include_field_values: true,
            include_diagnostics: false,
            include_modal_results: false,
            include_transient_results: false,
            include_nonlinear_results: false,
            include_electromagnetic_results: false,
            ..Self::default()
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnalysisFieldKind {
    Scalar,
    Vector,
    Tensor,
    Unknown,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnalysisFieldStorage {
    HostF64,
    DeviceRef,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnalysisFieldLocation {
    Node,
    Element,
    Edge,
    BoundaryFace,
    InterfaceFace,
    Mode,
    Global,
    #[default]
    Unknown,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisFieldDescriptor {
    pub field_id: String,
    #[serde(default)]
    pub family: String,
    #[serde(default)]
    pub quantity: String,
    pub class_name: String,
    pub kind: AnalysisFieldKind,
    pub dtype: String,
    #[serde(default)]
    pub unit: Option<String>,
    #[serde(default)]
    pub location: AnalysisFieldLocation,
    pub shape: Vec<usize>,
    pub element_count: usize,
    pub component_count: Option<usize>,
    pub residency: String,
    pub storage: AnalysisFieldStorage,
    pub size_bytes: Option<u64>,
}

impl AnalysisFieldDescriptor {
    pub fn from_field(field: &AnalysisField) -> Self {
        let storage = match &field.values {
            AnalysisFieldValues::HostF64(_) => AnalysisFieldStorage::HostF64,
            AnalysisFieldValues::DeviceRef(_) => AnalysisFieldStorage::DeviceRef,
        };
        let kind = infer_field_kind(&field.field_id, &field.shape);
        let class_name = match kind {
            AnalysisFieldKind::Scalar => "fea.ScalarField",
            AnalysisFieldKind::Vector => "fea.VectorField",
            AnalysisFieldKind::Tensor => "fea.TensorField",
            AnalysisFieldKind::Unknown => "fea.Field",
        }
        .to_string();
        let residency = match storage {
            AnalysisFieldStorage::HostF64 => "cpu",
            AnalysisFieldStorage::DeviceRef => "gpu",
        }
        .to_string();
        let element_count = field.element_count();
        Self {
            field_id: field.field_id.clone(),
            family: infer_field_family(&field.field_id).to_string(),
            quantity: infer_field_quantity(&field.field_id).to_string(),
            class_name,
            kind,
            dtype: "double".to_string(),
            unit: infer_field_unit(&field.field_id).map(str::to_string),
            location: infer_field_location(&field.field_id),
            shape: field.shape.clone(),
            element_count,
            component_count: infer_component_count(&field.field_id, &field.shape),
            residency,
            storage,
            size_bytes: element_count
                .checked_mul(std::mem::size_of::<f64>())
                .map(|bytes| bytes as u64),
        }
    }
}

fn infer_field_family(field_id: &str) -> &str {
    field_id
        .split_once('.')
        .map_or("unknown", |(family, _)| family)
}

fn infer_field_quantity(field_id: &str) -> &str {
    let Some((_, rest)) = field_id.split_once('.') else {
        return field_id;
    };
    let Some((quantity, suffix)) = rest.rsplit_once('.') else {
        return rest;
    };
    if suffix.chars().all(|ch| ch.is_ascii_digit()) {
        quantity
    } else {
        rest
    }
}

fn infer_field_unit(field_id: &str) -> Option<&'static str> {
    let normalized = field_id.to_ascii_lowercase();
    if normalized.contains("frequency_hz") || normalized.contains("frequency_response") {
        return Some("Hz");
    }
    if normalized.contains("eigenvalue") {
        return Some("rad^2/s^2");
    }
    if normalized.contains("sound_pressure_level_db") {
        return Some("dB");
    }
    if normalized.contains("temperature_gradient") {
        return Some("K/m");
    }
    if normalized.contains("temperature") || normalized.contains("temperature_jump") {
        return Some("K");
    }
    if normalized.contains("electric_potential") {
        return Some("V");
    }
    if normalized.contains("electric_field") {
        return Some("V/m");
    }
    if normalized.contains("electric_flux_density") {
        return Some("C/m^2");
    }
    if normalized.contains("current_density") {
        return Some("A/m^2");
    }
    if normalized.contains("vector_potential") {
        return Some("Wb/m");
    }
    if normalized.contains("magnetic_flux_density") {
        return Some("T");
    }
    if normalized.contains("magnetic_field") {
        return Some("A/m");
    }
    if normalized.contains("poynting_vector")
        || normalized.contains("boundary_heat_flux")
        || normalized.contains("interface_heat_flux")
        || normalized.ends_with(".heat_flux")
        || normalized.contains(".heat_flux.")
    {
        return Some("W/m^2");
    }
    if normalized.contains("power_loss_density")
        || normalized.contains("joule_heat")
        || normalized.contains("heat_source")
    {
        return Some("W/m^3");
    }
    if normalized.contains("energy_density") {
        return Some("J/m^3");
    }
    if normalized.contains("kinetic_energy")
        || normalized.contains("strain_energy")
        || normalized.contains("total_strain_energy")
    {
        return Some("J");
    }
    if normalized.contains("modal_mass") {
        return Some("kg");
    }
    if normalized.contains("modal_stiffness") {
        return Some("N/m");
    }
    if normalized.contains("wall_shear_stress")
        || normalized.contains("contact_pressure")
        || normalized.contains("interface_pressure")
        || normalized.contains("fluid_pressure")
        || normalized.contains(".pressure")
        || normalized.contains("stress")
        || normalized.contains("traction")
        || normalized.contains("von_mises")
    {
        return Some("Pa");
    }
    if normalized.contains("reaction_force")
        || normalized.contains("beam_axial_force")
        || normalized.contains("beam_shear_force")
        || normalized.contains("shell_membrane_force")
        || normalized.contains("shell_transverse_shear")
    {
        return Some("N");
    }
    if normalized.contains("reaction_moment")
        || normalized.contains("beam_torsion_moment")
        || normalized.contains("beam_bending_moment")
        || normalized.contains("shell_bending_moment")
    {
        return Some("N*m");
    }
    if normalized.contains("rotation") {
        return Some("rad");
    }
    if normalized.contains("contact_gap")
        || normalized.contains("displacement")
        || normalized.contains("mode_shape")
    {
        return Some("m");
    }
    if normalized.contains("velocity") {
        return Some("m/s");
    }
    if normalized.contains("acceleration") {
        return Some("m/s^2");
    }
    if normalized.contains("strain")
        || normalized.contains("residual")
        || normalized.contains("equation_scale")
        || normalized.contains("load_factor")
        || normalized.contains("coupling_iteration_count")
        || normalized.contains("reynolds_number")
        || normalized.contains("phase")
        || normalized.contains("orthogonality")
        || normalized.contains("participation_factor")
        || normalized.contains("relative_frequency_separation")
    {
        return Some("1");
    }
    None
}

fn infer_field_location(field_id: &str) -> AnalysisFieldLocation {
    let normalized = field_id.to_ascii_lowercase();
    if normalized.contains("vector_potential") {
        return AnalysisFieldLocation::Edge;
    }
    if normalized.contains("wall_shear_stress") || normalized.contains("boundary_heat_flux") {
        return AnalysisFieldLocation::BoundaryFace;
    }
    if normalized.contains("interface_")
        || normalized.contains("contact_pressure")
        || normalized.contains("contact_gap")
    {
        return AnalysisFieldLocation::InterfaceFace;
    }
    if normalized.starts_with("modal.")
        && !normalized.starts_with("modal.mode_shape.")
        && !normalized.contains("orthogonality")
    {
        return AnalysisFieldLocation::Mode;
    }
    if normalized.contains("residual")
        || normalized.contains("equation_scale")
        || normalized.contains("energy")
        || normalized.contains("load_factor")
        || normalized.contains("coupling_iteration_count")
        || normalized.contains("orthogonality")
    {
        return AnalysisFieldLocation::Global;
    }
    if normalized.starts_with("acoustic.") {
        return AnalysisFieldLocation::Node;
    }
    if normalized.contains("beam_") || normalized.contains("shell_") {
        return AnalysisFieldLocation::Element;
    }
    if normalized.contains("temperature_gradient")
        || normalized.contains("heat_flux")
        || normalized.contains("heat_source")
        || normalized.contains("joule_heat")
        || normalized.contains("magnetic_flux_density")
        || normalized.contains("magnetic_field")
        || normalized.contains("electric_field")
        || normalized.contains("electric_flux_density")
        || normalized.contains("current_density")
        || normalized.contains("poynting_vector")
        || normalized.contains("vorticity")
        || normalized.contains("pressure")
        || normalized.contains("stress")
        || normalized.contains("strain")
        || normalized.contains("von_mises")
    {
        return AnalysisFieldLocation::Element;
    }
    if normalized.contains("displacement")
        || normalized.contains("mode_shape")
        || normalized.contains("temperature")
        || normalized.starts_with("acoustic.")
        || normalized.contains("electric_potential")
    {
        return AnalysisFieldLocation::Node;
    }
    AnalysisFieldLocation::Element
}

fn infer_field_kind(field_id: &str, shape: &[usize]) -> AnalysisFieldKind {
    let normalized = field_id.to_ascii_lowercase();
    if normalized.contains("magnitude") {
        return AnalysisFieldKind::Scalar;
    }
    if normalized.contains("heat_flux") {
        return match shape {
            [_, components] if (2..=3).contains(components) => AnalysisFieldKind::Vector,
            _ => AnalysisFieldKind::Scalar,
        };
    }
    if normalized.contains("beam_shear_force")
        || normalized.contains("beam_bending_moment")
        || normalized.contains("beam_bending_stress")
        || normalized.contains("shell_membrane_force")
        || normalized.contains("shell_bending_moment")
        || normalized.contains("shell_transverse_shear")
    {
        return AnalysisFieldKind::Vector;
    }
    if normalized.contains("shell_von_mises") {
        return AnalysisFieldKind::Scalar;
    }
    if normalized.contains("beam_axial_force")
        || normalized.contains("beam_torsion_moment")
        || normalized.contains("beam_torsion_stress")
    {
        return AnalysisFieldKind::Scalar;
    }
    if normalized.contains("temperature_gradient")
        || normalized.contains("wall_shear_stress")
        || normalized.contains("vorticity")
        || normalized.contains("velocity")
        || normalized.contains("traction")
        || normalized.contains("magnetic_field")
        || normalized.contains("electric_field")
        || normalized.contains("current_density")
    {
        return AnalysisFieldKind::Vector;
    }
    if normalized.contains("von_mises")
        || normalized.contains("equivalent_plastic_strain")
        || normalized.contains("pressure")
        || normalized.contains("phase")
        || normalized.contains("reynolds_number")
        || normalized.contains("temperature")
        || normalized.contains("energy")
        || normalized.contains("residual")
    {
        return AnalysisFieldKind::Scalar;
    }
    if normalized.contains("stress") || normalized.contains("strain") {
        return AnalysisFieldKind::Tensor;
    }
    if normalized.contains("orthogonality") {
        return AnalysisFieldKind::Tensor;
    }
    if normalized.contains("displacement")
        || normalized.contains("rotation")
        || normalized.contains("mode_shape")
        || normalized.contains("reaction_force")
        || normalized.contains("reaction_moment")
        || normalized.contains("vector")
        || normalized.contains("flux")
    {
        return AnalysisFieldKind::Vector;
    }
    match shape {
        [] | [_] => AnalysisFieldKind::Scalar,
        [_, 1] => AnalysisFieldKind::Scalar,
        [_, 2] | [_, 3] => AnalysisFieldKind::Vector,
        [_, _, ..] => AnalysisFieldKind::Tensor,
    }
}

fn infer_component_count(field_id: &str, shape: &[usize]) -> Option<usize> {
    let normalized = field_id.to_ascii_lowercase();
    if normalized.contains("magnitude") {
        return None;
    }
    if normalized.contains("equivalent_plastic_strain") {
        return None;
    }
    if normalized.contains("heat_flux") {
        return match shape {
            [_, components] if (2..=3).contains(components) => Some(*components),
            _ => None,
        };
    }
    if normalized.contains("beam_shear_force")
        || normalized.contains("beam_bending_moment")
        || normalized.contains("beam_bending_stress")
    {
        return Some(2);
    }
    if normalized.contains("shell_membrane_force") || normalized.contains("shell_bending_moment") {
        return Some(3);
    }
    if normalized.contains("shell_transverse_shear") {
        return Some(2);
    }
    if normalized.contains("shell_von_mises") {
        return None;
    }
    if normalized.contains("beam_axial_force")
        || normalized.contains("beam_torsion_moment")
        || normalized.contains("beam_torsion_stress")
    {
        return None;
    }
    if normalized.contains("temperature_gradient")
        || normalized.contains("wall_shear_stress")
        || normalized.contains("vorticity")
        || normalized.contains("velocity")
        || normalized.contains("traction")
        || normalized.contains("magnetic_field")
        || normalized.contains("electric_field")
        || normalized.contains("current_density")
    {
        return Some(
            shape
                .last()
                .copied()
                .filter(|value| (2..=3).contains(value))
                .unwrap_or(3),
        );
    }
    if normalized.contains("von_mises")
        || normalized.contains("equivalent_plastic_strain")
        || normalized.contains("pressure")
        || normalized.contains("phase")
        || normalized.contains("reynolds_number")
        || normalized.contains("temperature")
        || normalized.contains("energy")
        || normalized.contains("residual")
    {
        return None;
    }
    if normalized.contains("displacement")
        || normalized.contains("rotation")
        || normalized.contains("mode_shape")
        || normalized.contains("reaction_force")
        || normalized.contains("reaction_moment")
        || normalized.contains("particle_velocity")
        || normalized.contains("vector")
        || normalized.contains("flux")
    {
        return Some(
            shape
                .last()
                .copied()
                .filter(|value| (2..=6).contains(value))
                .unwrap_or(3),
        );
    }
    if normalized.contains("stress") || normalized.contains("strain") {
        return Some(
            shape
                .last()
                .copied()
                .filter(|value| *value == 6)
                .unwrap_or(6),
        );
    }
    if normalized.contains("orthogonality") {
        return None;
    }
    match shape {
        [_, count] if (1..=6).contains(count) => Some(*count),
        _ => None,
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisResultsSummary {
    pub field_count: usize,
    pub total_elements: usize,
    pub mode_count: usize,
    pub available_mode_indices: Vec<usize>,
    pub min_frequency_hz: Option<f64>,
    pub max_frequency_hz: Option<f64>,
    pub max_modal_residual_norm: Option<f64>,
    pub first_mode_converged: Option<bool>,
    pub snapshot_count: usize,
    pub time_start_s: Option<f64>,
    pub time_end_s: Option<f64>,
    pub max_transient_residual_norm: Option<f64>,
    pub final_step_converged: Option<bool>,
    pub increment_count: usize,
    pub failed_increment_count: Option<usize>,
    pub max_nonlinear_residual_norm: Option<f64>,
    pub max_nonlinear_increment_norm: Option<f64>,
    pub max_nonlinear_iteration_count: Option<usize>,
    pub final_increment_converged: Option<bool>,
    pub nonlinear_line_search_backtracks: Option<usize>,
    pub nonlinear_max_backtracks_per_increment: Option<usize>,
    pub nonlinear_tangent_rebuild_count: Option<usize>,
    pub nonlinear_iteration_spike_count: Option<usize>,
    pub nonlinear_convergence_stall_count: Option<usize>,
    pub nonlinear_backtrack_burst_count: Option<usize>,
    pub prep_calibration_profile: Option<String>,
    pub prep_calibration_fingerprint: Option<u64>,
    pub prep_acceptance_score: Option<f64>,
    pub prep_acceptance_passed: Option<bool>,
    pub prep_acceptance_fingerprint: Option<u64>,
    pub thermo_coupling_enabled: Option<bool>,
    pub thermo_coupling_fingerprint: Option<u64>,
    pub thermo_constitutive_temperature_factor: Option<f64>,
    pub thermo_effective_modulus_scale: Option<f64>,
    pub thermo_constitutive_material_spread_ratio: Option<f64>,
    pub thermo_assignment_heterogeneity_index: Option<f64>,
    pub thermo_region_delta_count: Option<f64>,
    pub thermo_spatial_coverage_ratio: Option<f64>,
    pub thermo_field_extrapolation_ratio: Option<f64>,
    pub thermo_field_clamp_ratio: Option<f64>,
    pub thermo_transient_severity: Option<f64>,
    pub thermo_nonlinear_severity: Option<f64>,
    pub electro_thermal_coupling_enabled: Option<bool>,
    pub electro_thermal_coupling_fingerprint: Option<u64>,
    pub electro_joule_heating_scale: Option<f64>,
    pub electro_conductivity_spread_ratio: Option<f64>,
    pub electro_transient_severity: Option<f64>,
    pub electro_transient_time_scale_mean: Option<f64>,
    pub electro_nonlinear_severity: Option<f64>,
    pub electro_nonlinear_time_scale_mean: Option<f64>,
    pub plastic_nonlinear_severity: Option<f64>,
    pub plastic_nonlinear_severity_mean: Option<f64>,
    pub plastic_load_realization_ratio: Option<f64>,
    pub plastic_load_amplification_ratio: Option<f64>,
    pub contact_nonlinear_severity: Option<f64>,
    pub contact_nonlinear_severity_mean: Option<f64>,
    pub contact_load_realization_ratio: Option<f64>,
    pub contact_load_amplification_ratio: Option<f64>,
    pub thermal_max_residual_norm: Option<f64>,
    pub thermal_min_temperature_k: Option<f64>,
    pub thermal_max_temperature_k: Option<f64>,
    pub thermal_conductivity_spread_ratio: Option<f64>,
    pub thermal_heat_capacity_spread_ratio: Option<f64>,
    pub thermal_spatial_gradient_index: Option<f64>,
    pub thermal_monotonic_response_fraction: Option<f64>,
    pub thermal_response_realization_ratio: Option<f64>,
    pub electromagnetic_enabled: Option<bool>,
    pub electromagnetic_formulation_coverage_ratio: Option<f64>,
    pub electromagnetic_magnetostatic_curl_curl_coverage_ratio: Option<f64>,
    pub electromagnetic_magnetoquasistatic_eddy_current_coverage_ratio: Option<f64>,
    pub electromagnetic_full_wave_displacement_current_coverage_ratio: Option<f64>,
    pub electromagnetic_displacement_to_conduction_ratio: Option<f64>,
    pub electromagnetic_material_frequency_response_coverage_ratio: Option<f64>,
    pub electromagnetic_reference_frequency_hz: Option<f64>,
    pub electromagnetic_applied_current_a: Option<f64>,
    pub electromagnetic_solve_quality: Option<f64>,
    pub electromagnetic_conductivity_spread_ratio: Option<f64>,
    pub electromagnetic_relative_permittivity_spread_ratio: Option<f64>,
    pub electromagnetic_relative_permeability_spread_ratio: Option<f64>,
    pub electromagnetic_material_heterogeneity_index: Option<f64>,
    pub electromagnetic_assignment_coverage_ratio: Option<f64>,
    pub electromagnetic_assigned_coefficient_coverage_ratio: Option<f64>,
    pub electromagnetic_region_coefficient_contrast_index: Option<f64>,
    pub electromagnetic_condition_number_estimate: Option<f64>,
    pub electromagnetic_source_realization_ratio: Option<f64>,
    pub electromagnetic_source_region_coverage_ratio: Option<f64>,
    pub electromagnetic_source_material_alignment_ratio: Option<f64>,
    pub electromagnetic_source_localization_ratio: Option<f64>,
    pub electromagnetic_source_overlap_ratio: Option<f64>,
    pub electromagnetic_source_interference_index: Option<f64>,
    pub electromagnetic_boundary_anchor_ratio: Option<f64>,
    pub electromagnetic_boundary_condition_localization_ratio: Option<f64>,
    pub electromagnetic_ground_anchor_effectiveness_ratio: Option<f64>,
    pub electromagnetic_insulation_leakage_ratio: Option<f64>,
    pub electromagnetic_flux_divergence_ratio: Option<f64>,
    pub electromagnetic_energy_imbalance_ratio: Option<f64>,
    pub electromagnetic_boundary_energy_ratio: Option<f64>,
    pub electromagnetic_boundary_penalty_conditioning_contribution: Option<f64>,
    pub electromagnetic_source_region_energy_consistency_ratio: Option<f64>,
    pub electromagnetic_real_residual_norm: Option<f64>,
    pub electromagnetic_imag_residual_norm: Option<f64>,
    pub electromagnetic_sweep_count: Option<f64>,
    pub electromagnetic_resonance_peak_frequency_hz: Option<f64>,
    pub electromagnetic_resonance_peak_flux_density: Option<f64>,
    pub electromagnetic_resonance_bandwidth_hz: Option<f64>,
    pub electromagnetic_resonance_quality_factor: Option<f64>,
    pub electromagnetic_resonance_flux_gain: Option<f64>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisResultsData {
    #[serde(default)]
    pub field_descriptors: Vec<AnalysisFieldDescriptor>,
    pub fields: Vec<AnalysisField>,
    pub modal_results: Option<ModalResultsData>,
    #[serde(default)]
    pub thermal_results: Option<ThermalResultsData>,
    pub transient_results: Option<TransientResultsData>,
    pub nonlinear_results: Option<NonlinearResultsData>,
    #[serde(default)]
    pub electromagnetic_results: Option<ElectromagneticResultsData>,
    pub diagnostics: Option<Vec<FeaDiagnostic>>,
    pub run_status: RunStatus,
    pub publishable: bool,
    pub quality_reasons: Vec<QualityReason>,
    pub provenance: RunProvenance,
    pub summary: AnalysisResultsSummary,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisResultsCompareQuery {
    pub baseline_run_id: String,
    pub candidate_run_id: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisResultsCompareData {
    pub baseline_run_id: String,
    pub candidate_run_id: String,
    pub publishable_changed: bool,
    pub run_status_changed: bool,
    pub quality_reason_count_delta: i64,
    pub failed_increment_delta: Option<i64>,
    pub max_iteration_delta: Option<i64>,
    pub nonlinear_spike_count_delta: Option<i64>,
    pub nonlinear_stall_count_delta: Option<i64>,
    pub solve_ms_delta: Option<f64>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisTrendsQuery {
    pub window_size: usize,
}

impl Default for AnalysisTrendsQuery {
    fn default() -> Self {
        Self { window_size: 16 }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisStudySpec {
    pub study_id: String,
    pub geometry: GeometryAsset,
    pub create_model_intent: AnalysisCreateModelIntentSpec,
    #[serde(default)]
    pub model: Option<AnalysisModel>,
    pub run_kind: AnalysisRunKind,
    pub backend: ComputeBackend,
    #[serde(default)]
    pub linear_static_run_options: Option<AnalysisRunOptions>,
    #[serde(default)]
    pub modal_run_options: Option<AnalysisModalRunOptions>,
    #[serde(default)]
    pub acoustic_run_options: Option<AnalysisAcousticRunOptions>,
    #[serde(default)]
    pub thermal_run_options: Option<AnalysisThermalRunOptions>,
    #[serde(default)]
    pub transient_run_options: Option<AnalysisTransientRunOptions>,
    #[serde(default)]
    pub cfd_run_options: Option<AnalysisCfdRunOptions>,
    #[serde(default)]
    pub cht_run_options: Option<AnalysisChtRunOptions>,
    #[serde(default)]
    pub fsi_run_options: Option<AnalysisFsiRunOptions>,
    #[serde(default)]
    pub nonlinear_run_options: Option<AnalysisNonlinearRunOptions>,
    #[serde(default)]
    pub electromagnetic_run_options: Option<AnalysisElectromagneticRunOptions>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisStudyValidateResult {
    pub valid: bool,
    pub issue_codes: Vec<String>,
    #[serde(default)]
    pub issues: Vec<AnalysisStudyIssue>,
    pub evidence_artifact_path: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisStudyIssue {
    pub code: String,
    pub message: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisStudyPlanData {
    pub study_id: String,
    pub model_id: String,
    pub run_kind: AnalysisRunKind,
    pub backend: ComputeBackend,
    #[serde(default)]
    pub electromagnetic_run_options: Option<AnalysisElectromagneticRunOptions>,
    #[serde(default)]
    pub run_options: serde_json::Value,
    pub operation_sequence: Vec<String>,
    pub run_operation: String,
    pub run_op_version: String,
    pub study_fingerprint: String,
    pub evidence_artifact_path: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisStudyRunData {
    pub study_id: String,
    pub model_id: String,
    pub run_kind: AnalysisRunKind,
    pub backend: ComputeBackend,
    #[serde(default)]
    pub electromagnetic_run_options: Option<AnalysisElectromagneticRunOptions>,
    #[serde(default)]
    pub run_options: serde_json::Value,
    #[serde(default)]
    pub prep_artifact_id: Option<String>,
    pub study_fingerprint: String,
    pub operation_sequence: Vec<String>,
    pub run_operation: String,
    pub run_op_version: String,
    pub run_id: String,
    pub run_status: RunStatus,
    pub publishable: bool,
    pub solver_convergence: QualityGate,
    pub result_quality: QualityGate,
    #[serde(default)]
    pub quality_reasons: Vec<QualityReason>,
    pub provenance: RunProvenance,
    pub evidence_artifact_path: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisStudySweepSpec {
    pub sweep_id: String,
    pub studies: Vec<AnalysisStudySpec>,
    #[serde(default = "default_study_sweep_fail_fast")]
    pub fail_fast: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisStudySweepValidateEntry {
    pub study_id: String,
    pub valid: bool,
    pub issue_codes: Vec<String>,
    #[serde(default)]
    pub issues: Vec<AnalysisStudyIssue>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisStudySweepValidateData {
    pub sweep_id: String,
    pub valid: bool,
    pub issue_codes: Vec<String>,
    pub study_entries: Vec<AnalysisStudySweepValidateEntry>,
    pub evidence_artifact_path: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisStudySweepPlanEntry {
    pub study_id: String,
    pub model_id: String,
    pub run_kind: AnalysisRunKind,
    pub backend: ComputeBackend,
    #[serde(default)]
    pub electromagnetic_run_options: Option<AnalysisElectromagneticRunOptions>,
    #[serde(default)]
    pub run_options: serde_json::Value,
    pub operation_sequence: Vec<String>,
    pub run_operation: String,
    pub run_op_version: String,
    pub study_fingerprint: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisStudySweepPlanData {
    pub sweep_id: String,
    pub study_count: usize,
    pub planned_count: usize,
    pub failed_count: usize,
    #[serde(default)]
    pub failure_entries: Vec<AnalysisStudySweepFailureEntry>,
    pub plan_entries: Vec<AnalysisStudySweepPlanEntry>,
    pub evidence_artifact_path: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisStudySweepRunEntry {
    pub study_id: String,
    pub run_kind: AnalysisRunKind,
    pub run_id: String,
    pub run_status: RunStatus,
    pub publishable: bool,
    pub run_operation: String,
    pub run_op_version: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisStudySweepData {
    pub sweep_id: String,
    pub study_count: usize,
    pub success_count: usize,
    pub failed_count: usize,
    #[serde(default)]
    pub failure_entries: Vec<AnalysisStudySweepFailureEntry>,
    pub run_entries: Vec<AnalysisStudySweepRunEntry>,
    pub evidence_artifact_path: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalysisStudySweepFailureEntry {
    pub study_id: String,
    pub study_index: usize,
    pub error_code: String,
    pub message: String,
}

fn default_study_sweep_fail_fast() -> bool {
    true
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnalysisRunKind {
    LinearStatic,
    Modal,
    Acoustic,
    Thermal,
    Transient,
    Cfd,
    Cht,
    Fsi,
    Nonlinear,
    Electromagnetic,
}

impl AnalysisCreateModelProfile {
    pub fn derived_run_kind(self) -> AnalysisRunKind {
        match self {
            AnalysisCreateModelProfile::LinearStaticStructural => AnalysisRunKind::LinearStatic,
            AnalysisCreateModelProfile::ThermoMechanicalCoupled => AnalysisRunKind::Transient,
            AnalysisCreateModelProfile::ThermalStandalone => AnalysisRunKind::Thermal,
            AnalysisCreateModelProfile::ModalStructural => AnalysisRunKind::Modal,
            AnalysisCreateModelProfile::AcousticHarmonic => AnalysisRunKind::Acoustic,
            AnalysisCreateModelProfile::TransientStructural => AnalysisRunKind::Transient,
            AnalysisCreateModelProfile::NonlinearStructural => AnalysisRunKind::Nonlinear,
            AnalysisCreateModelProfile::ElectromagneticStatic => AnalysisRunKind::Electromagnetic,
            AnalysisCreateModelProfile::CfdSteadyState
            | AnalysisCreateModelProfile::CfdTransient => AnalysisRunKind::Cfd,
            AnalysisCreateModelProfile::ChtCoupled => AnalysisRunKind::Cht,
            AnalysisCreateModelProfile::FsiCoupled => AnalysisRunKind::Fsi,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisTrendKindSummary {
    pub run_kind: AnalysisRunKind,
    pub sample_count: usize,
    pub median_solve_ms: Option<f64>,
    pub p95_solve_ms: Option<f64>,
    pub publishable_rate: f64,
    pub failed_increment_rate: Option<f64>,
    pub mean_spike_count: Option<f64>,
    pub mean_stall_count: Option<f64>,
    pub prep_acceptance_rate: Option<f64>,
    pub prep_calibration_fast_rate: Option<f64>,
    pub prep_calibration_balanced_rate: Option<f64>,
    pub prep_calibration_conservative_rate: Option<f64>,
    pub thermo_coupling_enabled_rate: Option<f64>,
    pub thermo_transient_warn_rate: Option<f64>,
    pub thermo_nonlinear_warn_rate: Option<f64>,
    pub thermo_spread_breach_rate: Option<f64>,
    pub thermo_heterogeneity_breach_rate: Option<f64>,
    pub electro_thermal_coupling_enabled_rate: Option<f64>,
    pub electro_transient_warn_rate: Option<f64>,
    pub electro_nonlinear_warn_rate: Option<f64>,
    pub plastic_nonlinear_warn_rate: Option<f64>,
    pub contact_nonlinear_warn_rate: Option<f64>,
    pub thermal_stability_warn_rate: Option<f64>,
    pub thermal_constitutive_warn_rate: Option<f64>,
    pub thermal_spread_breach_rate: Option<f64>,
    pub electromagnetic_solve_warn_rate: Option<f64>,
    pub electromagnetic_spread_breach_rate: Option<f64>,
    pub electromagnetic_heterogeneity_breach_rate: Option<f64>,
    pub electromagnetic_coverage_breach_rate: Option<f64>,
    pub electromagnetic_contrast_breach_rate: Option<f64>,
    pub electromagnetic_conditioning_breach_rate: Option<f64>,
    pub electromagnetic_source_realization_breach_rate: Option<f64>,
    pub electromagnetic_source_region_coverage_breach_rate: Option<f64>,
    pub electromagnetic_source_material_alignment_breach_rate: Option<f64>,
    pub electromagnetic_source_overlap_breach_rate: Option<f64>,
    pub electromagnetic_source_interference_breach_rate: Option<f64>,
    pub electromagnetic_boundary_anchor_breach_rate: Option<f64>,
    pub electromagnetic_boundary_localization_breach_rate: Option<f64>,
    pub electromagnetic_ground_effectiveness_breach_rate: Option<f64>,
    pub electromagnetic_insulation_leakage_breach_rate: Option<f64>,
    pub electromagnetic_divergence_breach_rate: Option<f64>,
    pub electromagnetic_energy_imbalance_breach_rate: Option<f64>,
    pub electromagnetic_boundary_energy_breach_rate: Option<f64>,
    pub electromagnetic_boundary_penalty_contribution_breach_rate: Option<f64>,
    pub electromagnetic_source_region_energy_consistency_breach_rate: Option<f64>,
    pub electromagnetic_real_residual_breach_rate: Option<f64>,
    pub electromagnetic_imag_residual_breach_rate: Option<f64>,
    pub electromagnetic_sweep_coverage_breach_rate: Option<f64>,
    pub electromagnetic_resonance_sharpness_breach_rate: Option<f64>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnalysisTrendsData {
    pub window_size: usize,
    pub summaries: Vec<AnalysisTrendKindSummary>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ModalResultsData {
    pub modal_payload_version: String,
    pub eigenvalues_hz: Vec<f64>,
    pub mode_shapes: Vec<AnalysisField>,
    pub residual_norms: Vec<f64>,
    pub mode_units: ModalFrequencyUnits,
    pub frequency_basis: ModalFrequencyBasis,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ThermalResultsData {
    pub thermal_payload_version: String,
    pub time_points_s: Vec<f64>,
    pub temperature_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub temperature_gradient_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub heat_flux_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub heat_source_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub boundary_heat_flux_snapshots: Vec<AnalysisField>,
    pub residual_norms: Vec<f64>,
    pub reference_temperature_k: f64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TransientResultsData {
    pub transient_payload_version: String,
    pub time_points_s: Vec<f64>,
    pub displacement_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub rotation_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub velocity_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub angular_velocity_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub acceleration_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub angular_acceleration_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub von_mises_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub kinetic_energy_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub strain_energy_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub residual_norm_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_temperature_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_thermal_strain_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_thermal_stress_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_displacement_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_von_mises_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_coupling_residual_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub electro_thermal_temperature_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub electro_thermal_thermal_residual_snapshots: Vec<AnalysisField>,
    pub residual_norms: Vec<f64>,
    pub integration_method: TransientIntegrationMethod,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NonlinearResultsData {
    pub nonlinear_payload_version: String,
    pub load_factors: Vec<f64>,
    pub displacement_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub rotation_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub von_mises_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub plastic_strain_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub equivalent_plastic_strain_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub contact_pressure_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub contact_gap_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub load_factor_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub residual_norm_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_temperature_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_thermal_strain_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_thermal_stress_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_displacement_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_von_mises_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub thermo_mechanical_coupling_residual_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub electro_thermal_temperature_snapshots: Vec<AnalysisField>,
    #[serde(default)]
    pub electro_thermal_thermal_residual_snapshots: Vec<AnalysisField>,
    pub residual_norms: Vec<f64>,
    #[serde(default)]
    pub increment_norms: Vec<f64>,
    #[serde(default)]
    pub iteration_counts: Vec<usize>,
    #[serde(default)]
    pub failed_increments: usize,
    #[serde(default)]
    pub line_search_backtracks: usize,
    #[serde(default)]
    pub max_line_search_backtracks_per_increment: usize,
    #[serde(default)]
    pub tangent_rebuild_count: usize,
    #[serde(default)]
    pub iteration_spike_count: usize,
    #[serde(default)]
    pub convergence_stall_count: usize,
    #[serde(default)]
    pub backtrack_burst_count: usize,
    pub method: NonlinearMethod,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ElectromagneticResultsData {
    pub electromagnetic_payload_version: String,
    pub reference_frequency_hz: f64,
    pub applied_current_a: f64,
    pub vector_potential_real: AnalysisField,
    pub vector_potential_imag: AnalysisField,
    pub magnetic_flux_density_real: AnalysisField,
    pub magnetic_flux_density_imag: AnalysisField,
    pub magnetic_flux_density_magnitude: AnalysisField,
    pub magnetic_field_real: AnalysisField,
    pub magnetic_field_imag: AnalysisField,
    pub current_density_real: AnalysisField,
    pub current_density_imag: AnalysisField,
    pub electric_field_real: AnalysisField,
    pub electric_field_imag: AnalysisField,
    pub power_loss_density: AnalysisField,
    pub energy_density: AnalysisField,
    pub residual_real: AnalysisField,
    pub residual_imag: AnalysisField,
    pub electric_flux_density_real: AnalysisField,
    pub electric_flux_density_imag: AnalysisField,
    pub poynting_vector_real: AnalysisField,
    pub poynting_vector_imag: AnalysisField,
    #[serde(default)]
    pub sweep_frequency_hz: Vec<f64>,
    #[serde(default)]
    pub sweep_peak_flux_density: Vec<f64>,
    #[serde(default)]
    pub sweep_solve_quality: Vec<f64>,
    #[serde(default)]
    pub resonance_peak_frequency_hz: Option<f64>,
    #[serde(default)]
    pub resonance_peak_flux_density: Option<f64>,
    #[serde(default)]
    pub resonance_bandwidth_hz: Option<f64>,
    #[serde(default)]
    pub resonance_quality_factor: Option<f64>,
    #[serde(default)]
    pub resonance_flux_gain: Option<f64>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TransientIntegrationMethod {
    ImplicitEuler,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NonlinearMethod {
    IncrementalNewtonRaphson,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModalFrequencyUnits {
    Hz,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModalFrequencyBasis {
    NativeEigenSolve,
}

pub(crate) fn format_precision_mode(mode: PrecisionMode) -> String {
    match mode {
        PrecisionMode::Fp32 => "fp32".to_string(),
        PrecisionMode::Fp64 => "fp64".to_string(),
        PrecisionMode::Mixed => "mixed".to_string(),
    }
}

pub(crate) fn format_quality_policy(mode: QualityPolicy) -> String {
    match mode {
        QualityPolicy::Strict => "strict".to_string(),
        QualityPolicy::Balanced => "balanced".to_string(),
        QualityPolicy::Exploratory => "exploratory".to_string(),
    }
}
