///|
pub(all) struct NativeStubSourcePhaseRule {
  phase : String
  files : Array[String]
  expected_symbols : Array[String]
} derive(Eq, Debug)

///|
pub(all) struct NativeStubSourcePhasePolicyReport {
  rule_count : Int
  actual_fact_count : Int
  actual_phase_count : Int
  expected_symbol_count : Int
  phase1_symbol_count : Int
  phase2_symbol_count : Int
  phase3_symbol_count : Int
  missing_symbol_count : Int
  extra_symbol_count : Int
  unknown_file_count : Int
  duplicate_symbol_fact_count : Int
  replaces_file_reading : Bool
  replaces_function_extraction : Bool
  live_io_evidence : Bool
} derive(Eq, Debug)

///|
pub fn NativeStubSourcePhasePolicyReport::passes(
  self : NativeStubSourcePhasePolicyReport,
) -> Bool {
  self.rule_count == 3 &&
  self.actual_phase_count == self.rule_count &&
  self.expected_symbol_count > 0 &&
  self.phase1_symbol_count > 0 &&
  self.phase2_symbol_count > 0 &&
  self.phase3_symbol_count > 0 &&
  self.missing_symbol_count == 0 &&
  self.extra_symbol_count == 0 &&
  self.unknown_file_count == 0 &&
  self.duplicate_symbol_fact_count == 0 &&
  !self.replaces_file_reading &&
  !self.replaces_function_extraction &&
  !self.live_io_evidence
}

///|
pub fn native_stub_source_phase_rules() -> Array[NativeStubSourcePhaseRule] {
  [
    {
      phase: "phase1-platform",
      files: ["stub_npcap_windows_loader.c", "stub_npcap_windows_common.c"],
      expected_symbols: [
        "lockwire_native_npcap_ensure", "lockwire_native_npcap_findalldevs", "lockwire_native_npcap_freealldevs",
        "lockwire_native_npcap_live_ensure", "lockwire_native_npcap_open_live_handle",
        "lockwire_native_npcap_close_handle", "lockwire_native_npcap_sendpacket_handle",
        "lockwire_native_npcap_next_ex_handle", "lockwire_native_npcap_geterr_handle",
        "lockwire_native_npcap_compile_filter_handle", "lockwire_native_npcap_setfilter_handle",
        "lockwire_native_npcap_freecode_handle", "lockwire_native_npcap_probe_implemented",
        "lockwire_native_npcap_runtime_available", "lockwire_native_npcap_live_handle_get",
        "lockwire_native_npcap_live_handle_alloc", "lockwire_native_npcap_live_handle_release",
        "lockwire_native_npcap_copy_moonbit_bytes_to_c_string",
      ],
    },
    {
      phase: "phase2-execution",
      files: [
        "stub_npcap_windows_listing.c", "stub_npcap_windows_live.c", "stub_npcap_windows_live_send.c",
        "stub_npcap_windows_live_filter.c", "stub_npcap_windows_live_capture.c",
      ],
      expected_symbols: [
        "lockwire_native_npcap_list_interfaces_text", "lockwire_native_npcap_live_adapter_implemented",
        "lockwire_native_npcap_open_live", "lockwire_native_npcap_close_live", "lockwire_native_npcap_send_packet",
        "lockwire_native_npcap_set_filter", "lockwire_native_npcap_capture_one",
      ],
    },
    {
      phase: "phase3-fallback",
      files: ["stub_npcap_unsupported.c"],
      expected_symbols: native_stub_npcap_public_roster(),
    },
  ]
}

///|
pub fn native_stub_expected_source_phase_facts() -> Array[
  NativeStubSourceFunctionFact,
] {
  let facts : Array[NativeStubSourceFunctionFact] = []
  push_source_phase_file_facts(facts, "stub_npcap_windows_loader.c", [
    "lockwire_native_npcap_ensure", "lockwire_native_npcap_findalldevs", "lockwire_native_npcap_freealldevs",
    "lockwire_native_npcap_live_ensure", "lockwire_native_npcap_open_live_handle",
    "lockwire_native_npcap_close_handle", "lockwire_native_npcap_sendpacket_handle",
    "lockwire_native_npcap_next_ex_handle", "lockwire_native_npcap_geterr_handle",
    "lockwire_native_npcap_compile_filter_handle", "lockwire_native_npcap_setfilter_handle",
    "lockwire_native_npcap_freecode_handle", "lockwire_native_npcap_probe_implemented",
    "lockwire_native_npcap_runtime_available",
  ])
  push_source_phase_file_facts(facts, "stub_npcap_windows_common.c", [
    "lockwire_native_npcap_live_handle_get", "lockwire_native_npcap_live_handle_alloc",
    "lockwire_native_npcap_live_handle_release", "lockwire_native_npcap_copy_moonbit_bytes_to_c_string",
  ])
  push_source_phase_file_facts(facts, "stub_npcap_windows_listing.c", [
    "lockwire_native_npcap_list_interfaces_text",
  ])
  push_source_phase_file_facts(facts, "stub_npcap_windows_live.c", [
    "lockwire_native_npcap_live_adapter_implemented", "lockwire_native_npcap_open_live",
    "lockwire_native_npcap_close_live",
  ])
  push_source_phase_file_facts(facts, "stub_npcap_windows_live_send.c", [
    "lockwire_native_npcap_send_packet",
  ])
  push_source_phase_file_facts(facts, "stub_npcap_windows_live_filter.c", [
    "lockwire_native_npcap_set_filter",
  ])
  push_source_phase_file_facts(facts, "stub_npcap_windows_live_capture.c", [
    "lockwire_native_npcap_capture_one",
  ])
  push_source_phase_file_facts(
    facts,
    "stub_npcap_unsupported.c",
    native_stub_npcap_public_roster(),
  )
  facts
}

///|
pub fn native_stub_source_phase_policy_report(
  actual : Array[NativeStubSourceFunctionFact],
) -> NativeStubSourcePhasePolicyReport {
  native_stub_source_phase_policy_report_from_rules(
    native_stub_source_phase_rules(),
    actual,
  )
}

///|
pub fn native_stub_source_phase_policy_report_from_rules(
  rules : Array[NativeStubSourcePhaseRule],
  actual : Array[NativeStubSourceFunctionFact],
) -> NativeStubSourcePhasePolicyReport {
  {
    rule_count: rules.length(),
    actual_fact_count: actual.length(),
    actual_phase_count: count_actual_source_phases(rules, actual),
    expected_symbol_count: count_expected_source_phase_symbols(rules),
    phase1_symbol_count: count_source_phase_symbols(
      rules, actual, "phase1-platform",
    ),
    phase2_symbol_count: count_source_phase_symbols(
      rules, actual, "phase2-execution",
    ),
    phase3_symbol_count: count_source_phase_symbols(
      rules, actual, "phase3-fallback",
    ),
    missing_symbol_count: count_missing_source_phase_symbols(rules, actual),
    extra_symbol_count: count_extra_source_phase_symbols(rules, actual),
    unknown_file_count: count_unknown_source_phase_files(rules, actual),
    duplicate_symbol_fact_count: count_duplicate_source_phase_facts(actual),
    replaces_file_reading: false,
    replaces_function_extraction: false,
    live_io_evidence: false,
  }
}

///|
fn native_stub_npcap_public_roster() -> Array[String] {
  [
    "lockwire_native_npcap_probe_implemented", "lockwire_native_npcap_runtime_available",
    "lockwire_native_npcap_list_interfaces_text", "lockwire_native_npcap_live_adapter_implemented",
    "lockwire_native_npcap_open_live", "lockwire_native_npcap_send_packet", "lockwire_native_npcap_set_filter",
    "lockwire_native_npcap_capture_one", "lockwire_native_npcap_close_live",
  ]
}

///|
fn push_source_phase_file_facts(
  facts : Array[NativeStubSourceFunctionFact],
  file : String,
  symbols : Array[String],
) -> Unit {
  for symbol in symbols {
    facts.push({ file, symbol })
  }
}

///|
fn count_actual_source_phases(
  rules : Array[NativeStubSourcePhaseRule],
  actual : Array[NativeStubSourceFunctionFact],
) -> Int {
  let phases : Array[String] = []
  for fact in actual {
    let phase = source_phase_for_file(rules, fact.file)
    if phase != "" && !source_phase_contains_string(phases, phase) {
      phases.push(phase)
    }
  }
  phases.length()
}

///|
fn count_expected_source_phase_symbols(
  rules : Array[NativeStubSourcePhaseRule],
) -> Int {
  let mut count = 0
  for rule in rules {
    count += rule.expected_symbols.length()
  }
  count
}

///|
fn count_source_phase_symbols(
  rules : Array[NativeStubSourcePhaseRule],
  actual : Array[NativeStubSourceFunctionFact],
  phase : String,
) -> Int {
  let symbols : Array[String] = []
  for rule in rules {
    if rule.phase == phase {
      for fact in actual {
        if source_phase_rule_contains_file(rule, fact.file) &&
          !source_phase_contains_string(symbols, fact.symbol) {
          symbols.push(fact.symbol)
        }
      }
    }
  }
  symbols.length()
}

///|
fn count_missing_source_phase_symbols(
  rules : Array[NativeStubSourcePhaseRule],
  actual : Array[NativeStubSourceFunctionFact],
) -> Int {
  let mut count = 0
  for rule in rules {
    for symbol in rule.expected_symbols {
      if !source_phase_contains_symbol(actual, rule, symbol) {
        count += 1
      }
    }
  }
  count
}

///|
fn count_extra_source_phase_symbols(
  rules : Array[NativeStubSourcePhaseRule],
  actual : Array[NativeStubSourceFunctionFact],
) -> Int {
  let mut count = 0
  for fact in actual {
    let mut known_file = false
    let mut allowed = false
    for rule in rules {
      if source_phase_rule_contains_file(rule, fact.file) {
        known_file = true
        if source_phase_contains_string(rule.expected_symbols, fact.symbol) {
          allowed = true
        }
      }
    }
    if known_file && !allowed {
      count += 1
    }
  }
  count
}

///|
fn count_unknown_source_phase_files(
  rules : Array[NativeStubSourcePhaseRule],
  actual : Array[NativeStubSourceFunctionFact],
) -> Int {
  let files : Array[String] = []
  for fact in actual {
    if !source_phase_file_is_known(rules, fact.file) &&
      !source_phase_contains_string(files, fact.file) {
      files.push(fact.file)
    }
  }
  files.length()
}

///|
fn count_duplicate_source_phase_facts(
  actual : Array[NativeStubSourceFunctionFact],
) -> Int {
  let seen : Array[NativeStubSourceFunctionFact] = []
  let mut count = 0
  for fact in actual {
    if source_phase_contains_fact(seen, fact.file, fact.symbol) {
      count += 1
    } else {
      seen.push(fact)
    }
  }
  count
}

///|
fn source_phase_contains_symbol(
  actual : Array[NativeStubSourceFunctionFact],
  rule : NativeStubSourcePhaseRule,
  symbol : String,
) -> Bool {
  for fact in actual {
    if fact.symbol == symbol && source_phase_rule_contains_file(rule, fact.file) {
      return true
    }
  }
  false
}

///|
fn source_phase_for_file(
  rules : Array[NativeStubSourcePhaseRule],
  file : String,
) -> String {
  for rule in rules {
    if source_phase_rule_contains_file(rule, file) {
      return rule.phase
    }
  }
  ""
}

///|
fn source_phase_file_is_known(
  rules : Array[NativeStubSourcePhaseRule],
  file : String,
) -> Bool {
  for rule in rules {
    if source_phase_rule_contains_file(rule, file) {
      return true
    }
  }
  false
}

///|
fn source_phase_rule_contains_file(
  rule : NativeStubSourcePhaseRule,
  file : String,
) -> Bool {
  for got in rule.files {
    if got == file {
      return true
    }
  }
  false
}

///|
fn source_phase_contains_fact(
  facts : Array[NativeStubSourceFunctionFact],
  file : String,
  symbol : String,
) -> Bool {
  for fact in facts {
    if fact.file == file && fact.symbol == symbol {
      return true
    }
  }
  false
}

///|
fn source_phase_contains_string(items : Array[String], item : String) -> Bool {
  for got in items {
    if got == item {
      return true
    }
  }
  false
}