// Copyright (C) 2026 Jason M. Schwefel. All Rights Reserved.
package com.coldboreballisticsllc.scopedope.ballistics

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class TestConditions(
    @SerialName("altitude_ft")      val altitudeFt:      Double = 0.0,
    @SerialName("temp_f")           val tempF:           Double = 59.0,
    @SerialName("pressure_hpa")     val pressureHpa:     Double = 0.0,
    @SerialName("humidity_pct")     val humidityPct:     Double = 0.0,
    @SerialName("confirmed_fields") val confirmedFields: List<String> = emptyList(),
)

@Serializable
data class TestDopePoint(
    val yards:  Double,
    val mil:    Double,
    val moa:    Double  = 0.0,
    val source: String  = "",
    val fired:  Boolean = false,
)

@Serializable
data class FiredValidationPoint(
    val yards: Double,
    val mil:   Double,
    val moa:   Double = 0.0,
)

@Serializable
data class TestCase(
    val id:                                          String,
    val caliber:                                     String,
    @SerialName("load_name")          val loadName:  String,
    @SerialName("bc_model")           val bcModel:   String,
    val bc:                                          Double,
    @SerialName("bc_source")          val bcSource:  String          = "",
    @SerialName("mv_fps")             val mvFps:     Double,
    @SerialName("mv_source")          val mvSource:  String          = "",
    @SerialName("bullet_weight_gr")   val bulletWeightGr: Double     = 0.0,
    @SerialName("zero_yards")         val zeroYards: Double,
    @SerialName("sight_height_in")    val sightHeightIn: Double      = 1.5,
    @SerialName("twist_rate_in")      val twistRateIn: Double        = 0.0,
    val conditions:                                  TestConditions   = TestConditions(),
    @SerialName("dope_points")        val dopePoints: List<TestDopePoint> = emptyList(),
    @SerialName("fired_validation_points") val firedValidationPoints: List<FiredValidationPoint> = emptyList(),
    @SerialName("training_points_yards")   val trainingPointsYards:   List<Double> = emptyList(),
    val tier:                                        String           = "Sub-3",
    @SerialName("data_notes")         val dataNotes: String          = "",
    // Real-world field DOPE: scored against the wider scope-click band instead of the
    // strict calibrated-reference band. Set by the real-data importer.
    val realworld:                                   Boolean          = false,
)

/** Aggregated stats for a single k value at a single holdout distance. */
data class KBandStats(
    val k:               Int,
    val nCombos:         Int,
    val meanAbs:         Double,
    val medianAbs:       Double,
    val p95Abs:          Double,
    val maxAbs:          Double,
    val nInterior:       Int,
    val maxAbsInterior:  Double,   // Double.NaN if none
    val nExtrap:         Int,
    val maxAbsExtrap:    Double,   // Double.NaN if none
    val verdictInterior: String,
    val verdict:         String,
)

/** Per-distance aggregation across all C(N,k) combinations that held this distance out. */
data class DistStats(
    val distYd:       Double,
    val actualMil:    Double,
    val physicsMil:   Double,
    val deltaPhysics: Double,
    val nCombos:      Int,
    val meanAbs:      Double,
    val medianAbs:    Double,
    val p95Abs:       Double,
    val maxAbs:       Double,
    val maxDelta:     Double,
    val worstK:       Int,
    val byK:          Map<Int, KBandStats>,
    val flags:        List<String>,
    val verdictK1:    String,   // k=1 interior holdouts — primary verdict
    val verdictAll:   String,   // worst-case across all k
)

/** Full result for one test case. */
data class CaseResult(
    val caseId:     String,
    val caliber:    String,
    val loadName:   String,
    val tier:       String,
    val envPct:     Double,
    val hasFired:   Boolean,
    val mvSource:   String,
    val dataNotes:  String,
    val distStats:  List<DistStats>,
    val realworld:  Boolean = false,
    val skipped:    Boolean = false,
    val skipReason: String  = "",
    val looSkipped: Boolean = false,
    val looSkipWhy: String  = "",
)

/** Result for the .17 HMR fired-impact validation. */
data class FiredValidationResult(
    val caseId:    String,
    val firedPts:  List<FiredPoint>,
)

data class FiredPoint(
    val distYd:       Double,
    val actualMil:    Double,
    val physicsMil:   Double,
    val dopeMil:      Double,
    val deltaPhysics: Double,
    val deltaDope:    Double,
    val flags:        List<String>,
)

/** Result for the .308 175gr altitude/temperature sensitivity test. */
data class AltSensResult(
    val runALabel: String,
    val runBLabel: String,
    val rows:      List<AltSensRow>,
)

data class AltSensRow(
    val distYd:  Double,
    val runAMil: Double,
    val runBMil: Double,
    val delta:   Double,
)
