///|
pub(all) struct NativeStubFfiShimFact {
  file : String
  wrapper : String
  extern_target : String
} derive(Eq, Debug)

///|
pub(all) struct NativeStubFfiShimPolicyReport {
  expected_fact_count : Int
  actual_fact_count : Int
  native_fact_count : Int
  fallback_fact_count : Int
  missing_fact_count : Int
  extra_fact_count : Int
  duplicate_fact_count : Int
  fallback_native_binding_violation_count : Int
  replaces_file_reading : Bool
  replaces_wrapper_extraction : Bool
  replaces_extern_target_extraction : Bool
  live_io_evidence : Bool
} derive(Eq, Debug)

///|
pub fn NativeStubFfiShimPolicyReport::passes(
  self : NativeStubFfiShimPolicyReport,
) -> Bool {
  self.expected_fact_count > 0 &&
  self.actual_fact_count == self.expected_fact_count &&
  self.native_fact_count > 0 &&
  self.fallback_fact_count > 0 &&
  self.missing_fact_count == 0 &&
  self.extra_fact_count == 0 &&
  self.duplicate_fact_count == 0 &&
  self.fallback_native_binding_violation_count == 0 &&
  !self.replaces_file_reading &&
  !self.replaces_wrapper_extraction &&
  !self.replaces_extern_target_extraction &&
  !self.live_io_evidence
}

///|
pub fn native_stub_expected_ffi_shim_facts() -> Array[NativeStubFfiShimFact] {
  let facts : Array[NativeStubFfiShimFact] = []
  for entry in native_stub_capability_ledger() {
    for i in 0.. NativeStubFfiShimPolicyReport {
  native_stub_ffi_shim_policy_report_from_expected(
    native_stub_expected_ffi_shim_facts(),
    actual,
  )
}

///|
pub fn native_stub_ffi_shim_policy_report_from_expected(
  expected : Array[NativeStubFfiShimFact],
  actual : Array[NativeStubFfiShimFact],
) -> NativeStubFfiShimPolicyReport {
  {
    expected_fact_count: expected.length(),
    actual_fact_count: actual.length(),
    native_fact_count: count_ffi_shim_native_facts(actual),
    fallback_fact_count: count_ffi_shim_fallback_facts(actual),
    missing_fact_count: count_missing_ffi_shim_facts(expected, actual),
    extra_fact_count: count_missing_ffi_shim_facts(actual, expected),
    duplicate_fact_count: count_duplicate_ffi_shim_facts(actual),
    fallback_native_binding_violation_count: count_fallback_native_binding_violations(
      actual,
    ),
    replaces_file_reading: false,
    replaces_wrapper_extraction: false,
    replaces_extern_target_extraction: false,
    live_io_evidence: false,
  }
}

///|
fn count_ffi_shim_native_facts(facts : Array[NativeStubFfiShimFact]) -> Int {
  let mut count = 0
  for fact in facts {
    if fact.file.contains("_native.mbt") {
      count += 1
    }
  }
  count
}

///|
fn count_ffi_shim_fallback_facts(facts : Array[NativeStubFfiShimFact]) -> Int {
  let mut count = 0
  for fact in facts {
    if fact.file.contains("_fallback.mbt") {
      count += 1
    }
  }
  count
}

///|
fn count_missing_ffi_shim_facts(
  expected : Array[NativeStubFfiShimFact],
  actual : Array[NativeStubFfiShimFact],
) -> Int {
  let mut count = 0
  for fact in expected {
    if !contains_ffi_shim_fact(actual, fact) {
      count += 1
    }
  }
  count
}

///|
fn count_duplicate_ffi_shim_facts(facts : Array[NativeStubFfiShimFact]) -> Int {
  let unique : Array[NativeStubFfiShimFact] = []
  let mut count = 0
  for fact in facts {
    if contains_ffi_shim_fact(unique, fact) {
      count += 1
    } else {
      unique.push(fact)
    }
  }
  count
}

///|
fn count_fallback_native_binding_violations(
  facts : Array[NativeStubFfiShimFact],
) -> Int {
  let mut count = 0
  for fact in facts {
    if fact.file.contains("_fallback.mbt") && fact.extern_target != "" {
      count += 1
    }
  }
  count
}

///|
fn push_unique_ffi_shim_fact(
  facts : Array[NativeStubFfiShimFact],
  fact : NativeStubFfiShimFact,
) -> Unit {
  if !contains_ffi_shim_fact(facts, fact) {
    facts.push(fact)
  }
}

///|
fn contains_ffi_shim_fact(
  facts : Array[NativeStubFfiShimFact],
  fact : NativeStubFfiShimFact,
) -> Bool {
  for got in facts {
    if got == fact {
      return true
    }
  }
  false
}