///|
/// 比较运算符(用于文件级 min/max 统计剪枝)
pub(all) enum CompareOp {
  EqV
  GtV
  GeV
  LtV
  LeV
} derive(Eq, Debug)

///|
pub(all) struct ColumnPredicate {
  column : String
  op : CompareOp
  value : Double
} derive(Eq, Debug)

///|
/// 扫描过滤条件:分区等值过滤 + 单列统计剪枝
pub(all) struct ScanFilter {
  partitions : Array[(String, String)]
  predicate : ColumnPredicate?
} derive(Eq, Debug)

///|
/// 扫描结果(不读数据文件,只做文件级规划)
pub(all) struct ScanReport {
  files : Array[AddFile]
  skipped_partition : Int32
  skipped_stats : Int32
  matched_rows : Int64
  matched_bytes : Int64
} derive(Eq, Debug)

///|
fn partition_matches(f : AddFile, wanted : Array[(String, String)]) -> Bool {
  for pair in wanted {
    let (key, value) = pair
    let mut found = false
    for pv in f.partition_values {
      let (pk, pval) = pv
      if pk == key && pval == value {
        found = true
      }
    }
    if !found {
      return false
    }
  }
  true
}

///|
fn min_max_for_column(stats : String?, column : String) -> (Double?, Double?) {
  match stats {
    Some(text) => {
      let j = @json.parse(text) catch { _ => return (None, None) }
      let min_v = match json_field(j, "minValues") {
        Some(m) =>
          match json_field(m, column) {
            Some(v) => json_as_double(v)
            None => None
          }
        None => None
      }
      let max_v = match json_field(j, "maxValues") {
        Some(m) =>
          match json_field(m, column) {
            Some(v) => json_as_double(v)
            None => None
          }
        None => None
      }
      (min_v, max_v)
    }
    None => (None, None)
  }
}

///|
fn stats_prunes(f : AddFile, pred : ColumnPredicate) -> Bool {
  let (min_v, max_v) = min_max_for_column(f.stats, pred.column)
  match (min_v, max_v) {
    (Some(mn), Some(mx)) =>
      match pred.op {
        EqV => mx < pred.value || mn > pred.value
        GtV => mx <= pred.value
        GeV => mx < pred.value
        LtV => mn >= pred.value
        LeV => mn > pred.value
      }
    _ => false
  }
}

///|
/// 文件级扫描规划:分区过滤 + min/max 统计剪枝。
/// 不读取数据文件,因此可离线测试、也可用于"改动了多少行"这类审计场景。
pub fn scan(snapshot : Snapshot, filter : ScanFilter) -> ScanReport {
  let files : Array[AddFile] = []
  let mut skipped_partition = 0
  let mut skipped_stats = 0
  let mut matched_rows = 0L
  let mut matched_bytes = 0L
  for f in snapshot.files {
    if !partition_matches(f, filter.partitions) {
      skipped_partition = skipped_partition + 1
      continue
    }
    let pruned = match filter.predicate {
      Some(pred) => stats_prunes(f, pred)
      None => false
    }
    if pruned {
      skipped_stats = skipped_stats + 1
      continue
    }
    let rows = stats_num_records(f.stats)
    matched_rows = matched_rows + rows
    matched_bytes = matched_bytes + f.size
    files.push(f)
  }
  { files, skipped_partition, skipped_stats, matched_rows, matched_bytes, }
}