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

///|
pub(all) struct NativeStubSourceIncludeRule {
  file : String
  required_includes : Array[String]
  forbidden_includes : Array[String]
  forbid_source_includes : Bool
} derive(Eq, Debug)

///|
pub(all) struct NativeStubSourceIncludePolicyReport {
  rule_count : Int
  actual_file_count : Int
  missing_file_count : Int
  extra_file_count : Int
  required_include_count : Int
  missing_required_include_count : Int
  forbidden_include_count : Int
  present_forbidden_include_count : Int
  source_include_violation_count : Int
  duplicate_include_fact_count : Int
  replaces_file_reading : Bool
  replaces_include_extraction : Bool
  live_io_evidence : Bool
} derive(Eq, Debug)

///|
pub fn NativeStubSourceIncludePolicyReport::passes(
  self : NativeStubSourceIncludePolicyReport,
) -> Bool {
  self.rule_count > 0 &&
  self.actual_file_count == self.rule_count &&
  self.missing_file_count == 0 &&
  self.extra_file_count == 0 &&
  self.required_include_count > 0 &&
  self.missing_required_include_count == 0 &&
  self.present_forbidden_include_count == 0 &&
  self.source_include_violation_count == 0 &&
  self.duplicate_include_fact_count == 0 &&
  !self.replaces_file_reading &&
  !self.replaces_include_extraction &&
  !self.live_io_evidence
}

///|
pub fn native_stub_source_include_rules() -> Array[NativeStubSourceIncludeRule] {
  let rules : Array[NativeStubSourceIncludeRule] = []
  push_source_include_rules(
    rules,
    [
      "stub_common_status.c", "stub_common_handle.c", "stub_common_bytes.c", "stub_clock_windows.c",
      "stub_clock_posix.c", "stub_clock_unsupported.c",
    ],
    ["stub_common.h"],
    [],
  )
  push_source_include_rules(
    rules,
    ["stub_pcap_common.c", "stub_pcap_writer.c"],
    ["stub_pcap.h"],
    [],
  )
  push_source_include_rules(
    rules,
    [
      "stub_frame_pool_common.c", "stub_frame_pool_lifecycle.c", "stub_frame_pool_state.c",
      "stub_frame_pool_io.c",
    ],
    ["stub_frame_pool.h"],
    [],
  )
  push_source_include_rules(
    rules,
    [
      "stub_npcap_windows_loader.c", "stub_npcap_windows_listing.c", "stub_npcap_windows_common.c",
      "stub_npcap_windows_live.c", "stub_npcap_windows_live_send.c", "stub_npcap_windows_live_filter.c",
      "stub_npcap_windows_live_capture.c",
    ],
    ["stub_npcap_windows.h"],
    [],
  )
  rules.push(
    source_include_rule("stub_npcap_unsupported.c", ["stub_common.h"], [
      "stub_npcap_windows.h", "stub_npcap_windows_loader.c", "stub_npcap_windows_listing.c",
      "stub_npcap_windows_common.c", "stub_npcap_windows_live.c", "stub_npcap_windows_live_send.c",
      "stub_npcap_windows_live_filter.c", "stub_npcap_windows_live_capture.c",
    ]),
  )
  push_source_include_rules(
    rules,
    [
      "stub_raw_socket_linux_adapter.c", "stub_raw_socket_linux_send.c", "stub_raw_socket_linux_capture.c",
      "stub_raw_socket_linux_list.c", "stub_raw_socket_linux_common.c",
    ],
    ["stub_raw_socket_linux.h"],
    [],
  )
  rules.push(
    source_include_rule("stub_raw_socket_unsupported.c", ["stub_common.h"], [
      "stub_raw_socket_linux.h", "stub_raw_socket_linux_adapter.c", "stub_raw_socket_linux_common.c",
      "stub_raw_socket_linux_send.c", "stub_raw_socket_linux_capture.c", "stub_raw_socket_linux_list.c",
    ]),
  )
  push_source_include_rules(
    rules,
    [
      "stub_udp_windows_common.c", "stub_udp_windows_adapter.c", "stub_udp_windows_send.c",
      "stub_udp_windows_recv.c",
    ],
    ["stub_udp_windows.h"],
    [],
  )
  push_source_include_rules(
    rules,
    [
      "stub_udp_posix_common.c", "stub_udp_posix_adapter.c", "stub_udp_posix_send.c",
      "stub_udp_posix_recv.c",
    ],
    ["stub_udp_posix.h"],
    [],
  )
  rules.push(
    source_include_rule("stub_udp_unsupported.c", ["stub_common.h"], [
      "stub_udp_windows.h", "stub_udp_posix.h", "stub_udp_windows_common.c", "stub_udp_windows_adapter.c",
      "stub_udp_windows_send.c", "stub_udp_windows_recv.c", "stub_udp_posix_common.c",
      "stub_udp_posix_adapter.c", "stub_udp_posix_send.c", "stub_udp_posix_recv.c",
    ]),
  )
  push_source_include_rules(
    rules,
    [
      "stub_clock_millis_windows.c", "stub_clock_millis_posix.c", "stub_clock_millis_unsupported.c",
      "stub_sleep_windows.c", "stub_sleep_posix.c", "stub_sleep_unsupported.c", "stub_rt_affinity_windows.c",
      "stub_rt_affinity_unsupported.c",
    ],
    ["stub_common.h"],
    [],
  )
  push_source_include_rules(
    rules,
    [
      "stub_npcap_windows_local_mac.c", "stub_npcap_windows_nonblock.c", "stub_npcap_unsupported_live_ext.c",
    ],
    ["stub_npcap_windows.h"],
    [],
  )
  push_source_include_rules(
    rules,
    [
      "stub_raw_socket_linux_local_mac.c", "stub_raw_socket_linux_nonblock.c", "stub_raw_socket_unsupported_live_ext.c",
    ],
    ["stub_raw_socket_linux.h"],
    [],
  )
  rules
}

///|
pub fn native_stub_expected_source_include_facts() -> Array[
  NativeStubSourceIncludeFact,
] {
  let facts : Array[NativeStubSourceIncludeFact] = []
  for rule in native_stub_source_include_rules() {
    for header in rule.required_includes {
      facts.push({ file: rule.file, header })
    }
  }
  facts
}

///|
pub fn native_stub_source_include_policy_report(
  actual : Array[NativeStubSourceIncludeFact],
) -> NativeStubSourceIncludePolicyReport {
  native_stub_source_include_policy_report_from_rules(
    native_stub_source_include_rules(),
    actual,
  )
}

///|
pub fn native_stub_source_include_policy_report_from_rules(
  rules : Array[NativeStubSourceIncludeRule],
  actual : Array[NativeStubSourceIncludeFact],
) -> NativeStubSourceIncludePolicyReport {
  {
    rule_count: rules.length(),
    actual_file_count: count_actual_include_files(actual),
    missing_file_count: count_missing_include_files(rules, actual),
    extra_file_count: count_extra_include_files(rules, actual),
    required_include_count: count_required_includes(rules),
    missing_required_include_count: count_missing_required_includes(
      rules, actual,
    ),
    forbidden_include_count: count_forbidden_includes(rules),
    present_forbidden_include_count: count_present_forbidden_includes(
      rules, actual,
    ),
    source_include_violation_count: count_source_include_violations(
      rules, actual,
    ),
    duplicate_include_fact_count: count_duplicate_source_include_facts(actual),
    replaces_file_reading: false,
    replaces_include_extraction: false,
    live_io_evidence: false,
  }
}

///|
fn source_include_rule(
  file : String,
  required_includes : Array[String],
  forbidden_includes : Array[String],
) -> NativeStubSourceIncludeRule {
  { file, required_includes, forbidden_includes, forbid_source_includes: true }
}

///|
fn push_source_include_rules(
  rules : Array[NativeStubSourceIncludeRule],
  files : Array[String],
  required_includes : Array[String],
  forbidden_includes : Array[String],
) -> Unit {
  for file in files {
    rules.push(source_include_rule(file, required_includes, forbidden_includes))
  }
}

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

///|
fn count_missing_include_files(
  rules : Array[NativeStubSourceIncludeRule],
  actual : Array[NativeStubSourceIncludeFact],
) -> Int {
  let mut count = 0
  for rule in rules {
    if !source_include_contains_file(actual, rule.file) {
      count += 1
    }
  }
  count
}

///|
fn count_extra_include_files(
  rules : Array[NativeStubSourceIncludeRule],
  actual : Array[NativeStubSourceIncludeFact],
) -> Int {
  let seen : Array[String] = []
  let mut count = 0
  for fact in actual {
    if !source_include_contains_string(seen, fact.file) {
      seen.push(fact.file)
      if !source_include_contains_rule_file(rules, fact.file) {
        count += 1
      }
    }
  }
  count
}

///|
fn count_required_includes(rules : Array[NativeStubSourceIncludeRule]) -> Int {
  let mut count = 0
  for rule in rules {
    count += rule.required_includes.length()
  }
  count
}

///|
fn count_forbidden_includes(rules : Array[NativeStubSourceIncludeRule]) -> Int {
  let mut count = 0
  for rule in rules {
    count += rule.forbidden_includes.length()
  }
  count
}

///|
fn count_missing_required_includes(
  rules : Array[NativeStubSourceIncludeRule],
  actual : Array[NativeStubSourceIncludeFact],
) -> Int {
  let mut count = 0
  for rule in rules {
    for header in rule.required_includes {
      if !source_include_contains_fact(actual, rule.file, header) {
        count += 1
      }
    }
  }
  count
}

///|
fn count_present_forbidden_includes(
  rules : Array[NativeStubSourceIncludeRule],
  actual : Array[NativeStubSourceIncludeFact],
) -> Int {
  let mut count = 0
  for rule in rules {
    for header in rule.forbidden_includes {
      if source_include_contains_fact(actual, rule.file, header) {
        count += 1
      }
    }
  }
  count
}

///|
fn count_source_include_violations(
  rules : Array[NativeStubSourceIncludeRule],
  actual : Array[NativeStubSourceIncludeFact],
) -> Int {
  let mut count = 0
  for fact in actual {
    if fact.header.has_suffix(".c") &&
      source_include_rule_forbids_source_includes(rules, fact.file) {
      count += 1
    }
  }
  count
}

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

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

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

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

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

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

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