///|
pub(all) struct NativeStubSourceFunctionRule {
  file : String
  expected_symbols : Array[String]
} derive(Eq, Debug)

///|
pub(all) struct NativeStubSourceFunctionFact {
  file : String
  symbol : String
} derive(Eq, Debug)

///|
pub(all) struct NativeStubSourceFunctionPolicyReport {
  rule_count : Int
  actual_file_count : Int
  missing_file_count : Int
  extra_file_count : Int
  expected_symbol_count : Int
  missing_symbol_count : Int
  extra_symbol_count : Int
  duplicate_symbol_fact_count : Int
  replaces_file_reading : Bool
  replaces_function_extraction : Bool
  live_io_evidence : Bool
} derive(Eq, Debug)

///|
pub fn NativeStubSourceFunctionPolicyReport::passes(
  self : NativeStubSourceFunctionPolicyReport,
) -> Bool {
  self.rule_count > 0 &&
  self.actual_file_count == self.rule_count &&
  self.missing_file_count == 0 &&
  self.extra_file_count == 0 &&
  self.expected_symbol_count > 0 &&
  self.missing_symbol_count == 0 &&
  self.extra_symbol_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_function_rules() -> Array[
  NativeStubSourceFunctionRule,
] {
  let rules : Array[NativeStubSourceFunctionRule] = []
  rules.push(source_function_rule("stub_common.h", common_helper_symbols()))
  rules.push(
    source_function_rule("stub_common_status.c", ["lockwire_native_set_error"]),
  )
  rules.push(
    source_function_rule("stub_common_handle.c", [
      "lockwire_native_handle_alloc", "lockwire_native_handle_is_valid",
    ]),
  )
  rules.push(
    source_function_rule("stub_common_bytes.c", [
      "lockwire_native_u16_to_network", "lockwire_native_u16_from_network",
    ]),
  )
  rules.push(source_function_rule("stub_pcap.h", pcap_helper_symbols()))
  rules.push(source_function_rule("stub_pcap_common.c", pcap_helper_symbols()))
  rules.push(
    source_function_rule("stub_frame_pool.h", frame_pool_helper_symbols()),
  )
  rules.push(
    source_function_rule(
      "stub_frame_pool_common.c",
      frame_pool_helper_symbols(),
    ),
  )
  rules.push(
    source_function_rule("stub_npcap_windows.h", npcap_header_symbols()),
  )
  rules.push(
    source_function_rule(
      "stub_npcap_windows_common.c",
      npcap_common_helper_symbols(),
    ),
  )
  rules.push(
    source_function_rule("stub_raw_socket_linux.h", raw_socket_helper_symbols()),
  )
  rules.push(
    source_function_rule(
      "stub_raw_socket_linux_common.c",
      raw_socket_helper_symbols(),
    ),
  )
  rules.push(
    source_function_rule("stub_udp_windows.h", udp_windows_helper_symbols()),
  )
  rules.push(
    source_function_rule(
      "stub_udp_windows_common.c",
      udp_windows_helper_symbols(),
    ),
  )
  rules.push(
    source_function_rule("stub_udp_posix.h", udp_posix_helper_symbols()),
  )
  rules.push(
    source_function_rule("stub_udp_posix_common.c", udp_posix_helper_symbols()),
  )
  rules.push(
    source_function_rule("stub_clock_millis_windows.c", [
      "lockwire_native_monotonic_millis",
    ]),
  )
  rules.push(
    source_function_rule("stub_clock_millis_posix.c", [
      "lockwire_native_monotonic_millis",
    ]),
  )
  rules.push(
    source_function_rule("stub_clock_millis_unsupported.c", [
      "lockwire_native_monotonic_millis",
    ]),
  )
  rules.push(
    source_function_rule("stub_sleep_windows.c", [
      "lockwire_native_sleep_until_micros",
    ]),
  )
  rules.push(
    source_function_rule("stub_sleep_posix.c", [
      "lockwire_native_sleep_until_micros",
    ]),
  )
  rules.push(
    source_function_rule("stub_sleep_unsupported.c", [
      "lockwire_native_sleep_until_micros",
    ]),
  )
  rules.push(
    source_function_rule("stub_rt_affinity_windows.c", [
      "lockwire_native_win_rt_set_affinity", "lockwire_native_win_rt_supported",
    ]),
  )
  rules.push(
    source_function_rule("stub_rt_affinity_unsupported.c", [
      "lockwire_native_win_rt_set_affinity", "lockwire_native_win_rt_supported",
    ]),
  )
  rules.push(
    source_function_rule("stub_npcap_windows_local_mac.c", [
      "lockwire_native_npcap_local_mac",
    ]),
  )
  rules.push(
    source_function_rule("stub_npcap_windows_nonblock.c", [
      "lockwire_native_npcap_set_nonblock",
    ]),
  )
  rules.push(
    source_function_rule("stub_npcap_unsupported_live_ext.c", [
      "lockwire_native_npcap_local_mac", "lockwire_native_npcap_last_capture_micros",
      "lockwire_native_npcap_set_nonblock",
    ]),
  )
  rules.push(
    source_function_rule("stub_raw_socket_linux_local_mac.c", [
      "lockwire_native_raw_socket_local_mac",
    ]),
  )
  rules.push(
    source_function_rule("stub_raw_socket_linux_nonblock.c", [
      "lockwire_native_raw_socket_set_nonblock",
    ]),
  )
  rules.push(
    source_function_rule("stub_raw_socket_unsupported_live_ext.c", [
      "lockwire_native_raw_socket_local_mac", "lockwire_native_raw_socket_last_capture_micros",
      "lockwire_native_raw_socket_set_nonblock",
    ]),
  )
  rules
}

///|
pub fn native_stub_expected_source_function_facts() -> Array[
  NativeStubSourceFunctionFact,
] {
  let facts : Array[NativeStubSourceFunctionFact] = []
  for rule in native_stub_source_function_rules() {
    for symbol in rule.expected_symbols {
      facts.push({ file: rule.file, symbol })
    }
  }
  facts
}

///|
pub fn native_stub_source_function_policy_report(
  actual : Array[NativeStubSourceFunctionFact],
) -> NativeStubSourceFunctionPolicyReport {
  native_stub_source_function_policy_report_from_rules(
    native_stub_source_function_rules(),
    actual,
  )
}

///|
pub fn native_stub_source_function_policy_report_from_rules(
  rules : Array[NativeStubSourceFunctionRule],
  actual : Array[NativeStubSourceFunctionFact],
) -> NativeStubSourceFunctionPolicyReport {
  {
    rule_count: rules.length(),
    actual_file_count: count_actual_source_function_files(actual),
    missing_file_count: count_missing_source_function_files(rules, actual),
    extra_file_count: count_extra_source_function_files(rules, actual),
    expected_symbol_count: count_expected_source_function_symbols(rules),
    missing_symbol_count: count_missing_source_function_symbols(rules, actual),
    extra_symbol_count: count_extra_source_function_symbols(rules, actual),
    duplicate_symbol_fact_count: count_duplicate_source_function_facts(actual),
    replaces_file_reading: false,
    replaces_function_extraction: false,
    live_io_evidence: false,
  }
}

///|
fn source_function_rule(
  file : String,
  expected_symbols : Array[String],
) -> NativeStubSourceFunctionRule {
  { file, expected_symbols }
}

///|
fn common_helper_symbols() -> Array[String] {
  [
    "lockwire_native_set_error", "lockwire_native_handle_alloc", "lockwire_native_handle_is_valid",
    "lockwire_native_u16_to_network", "lockwire_native_u16_from_network",
  ]
}

///|
fn pcap_helper_symbols() -> Array[String] {
  [
    "lockwire_native_pcap_handle_get", "lockwire_native_pcap_handle_alloc", "lockwire_native_pcap_handle_release",
    "lockwire_native_pcap_ensure_parent_dir", "lockwire_native_pcap_timestamp", "lockwire_native_pcap_write_u16_le",
    "lockwire_native_pcap_write_u32_le",
  ]
}

///|
fn frame_pool_helper_symbols() -> Array[String] {
  [
    "lockwire_native_frame_pool_handle_alloc", "lockwire_native_frame_pool_handle_release",
    "lockwire_native_frame_pool_get", "lockwire_native_frame_pool_slot_is_valid",
    "lockwire_native_frame_pool_slot_ptr", "lockwire_native_frame_pool_bounds_ok",
  ]
}

///|
fn npcap_header_symbols() -> Array[String] {
  [
    "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_live_handle_get",
    "lockwire_native_npcap_live_handle_alloc", "lockwire_native_npcap_live_handle_release",
    "lockwire_native_npcap_copy_moonbit_bytes_to_c_string",
  ]
}

///|
fn npcap_common_helper_symbols() -> Array[String] {
  [
    "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",
  ]
}

///|
fn raw_socket_helper_symbols() -> Array[String] {
  [
    "lockwire_native_raw_socket_handle_get", "lockwire_native_raw_socket_handle_alloc",
    "lockwire_native_raw_socket_copy_interface_name", "lockwire_native_raw_socket_set_errno_error",
    "lockwire_native_raw_socket_handle_release",
  ]
}

///|
fn udp_windows_helper_symbols() -> Array[String] {
  [
    "lockwire_native_udp_windows_ensure", "lockwire_native_udp_windows_fill_sockaddr",
    "lockwire_native_udp_windows_handle_get", "lockwire_native_udp_windows_handle_alloc",
    "lockwire_native_udp_windows_handle_release",
  ]
}

///|
fn udp_posix_helper_symbols() -> Array[String] {
  [
    "lockwire_native_udp_posix_fill_sockaddr", "lockwire_native_udp_posix_handle_get",
    "lockwire_native_udp_posix_handle_alloc", "lockwire_native_udp_posix_handle_release",
  ]
}

///|
fn count_actual_source_function_files(
  facts : Array[NativeStubSourceFunctionFact],
) -> Int {
  let files : Array[String] = []
  for fact in facts {
    if !source_function_contains_string(files, fact.file) {
      files.push(fact.file)
    }
  }
  files.length()
}

///|
fn count_missing_source_function_files(
  rules : Array[NativeStubSourceFunctionRule],
  actual : Array[NativeStubSourceFunctionFact],
) -> Int {
  let mut count = 0
  for rule in rules {
    if !source_function_contains_file(actual, rule.file) {
      count += 1
    }
  }
  count
}

///|
fn count_extra_source_function_files(
  rules : Array[NativeStubSourceFunctionRule],
  actual : Array[NativeStubSourceFunctionFact],
) -> Int {
  let seen : Array[String] = []
  let mut count = 0
  for fact in actual {
    if !source_function_contains_string(seen, fact.file) {
      seen.push(fact.file)
      if !source_function_contains_rule_file(rules, fact.file) {
        count += 1
      }
    }
  }
  count
}

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

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

///|
fn count_extra_source_function_symbols(
  rules : Array[NativeStubSourceFunctionRule],
  actual : Array[NativeStubSourceFunctionFact],
) -> Int {
  let mut count = 0
  for fact in actual {
    if !source_function_rule_allows_fact(rules, fact.file, fact.symbol) {
      count += 1
    }
  }
  count
}

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

///|
fn source_function_rule_allows_fact(
  rules : Array[NativeStubSourceFunctionRule],
  file : String,
  symbol : String,
) -> Bool {
  for rule in rules {
    if rule.file == file &&
      source_function_contains_string(rule.expected_symbols, symbol) {
      return true
    }
  }
  false
}

///|
fn source_function_contains_rule_file(
  rules : Array[NativeStubSourceFunctionRule],
  file : String,
) -> Bool {
  for rule in rules {
    if rule.file == file {
      return true
    }
  }
  false
}

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

///|
fn source_function_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_function_contains_string(
  items : Array[String],
  item : String,
) -> Bool {
  for got in items {
    if got == item {
      return true
    }
  }
  false
}