///|
pub(all) struct NativeStubSourceTokenRule {
  file : String
  required_tokens : Array[String]
  forbidden_tokens : Array[String]
} derive(Eq, Debug)

///|
pub(all) struct NativeStubSourceTokenFact {
  file : String
  token : String
  present : Bool
} derive(Eq, Debug)

///|
pub(all) struct NativeStubSourceTokenPolicyReport {
  rule_count : Int
  actual_file_count : Int
  missing_file_count : Int
  extra_file_count : Int
  required_token_count : Int
  forbidden_token_count : Int
  missing_required_token_count : Int
  present_forbidden_token_count : Int
  unexpected_token_fact_count : Int
  duplicate_token_fact_count : Int
  replaces_file_reading : Bool
  replaces_token_extraction : Bool
  live_io_evidence : Bool
} derive(Eq, Debug)

///|
pub fn NativeStubSourceTokenPolicyReport::passes(
  self : NativeStubSourceTokenPolicyReport,
) -> Bool {
  self.rule_count > 0 &&
  self.actual_file_count == self.rule_count &&
  self.missing_file_count == 0 &&
  self.extra_file_count == 0 &&
  self.required_token_count > 0 &&
  self.forbidden_token_count > 0 &&
  self.missing_required_token_count == 0 &&
  self.present_forbidden_token_count == 0 &&
  self.unexpected_token_fact_count == 0 &&
  self.duplicate_token_fact_count == 0 &&
  !self.replaces_file_reading &&
  !self.replaces_token_extraction &&
  !self.live_io_evidence
}

///|
pub fn native_stub_source_token_rules() -> Array[NativeStubSourceTokenRule] {
  let rules : Array[NativeStubSourceTokenRule] = []
  rules.push(
    source_token_rule(
      "stub_common.h",
      [
        "#ifndef LOCKWIRE_NATIVE_STUB_COMMON_H", "#define LOCKWIRE_NATIVE_STUB_COMMON_H",
        "#define LOCKWIRE_NATIVE_OK 1",
      ],
      ["MOONBIT_FFI_EXPORT"],
    ),
  )
  rules.push(
    source_token_rule(
      "stub_pcap.h",
      [
        "#ifndef LOCKWIRE_NATIVE_STUB_PCAP_H", "#define LOCKWIRE_NATIVE_STUB_PCAP_H",
        "#define LOCKWIRE_MAX_PCAP_HANDLES 32",
      ],
      ["MOONBIT_FFI_EXPORT"],
    ),
  )
  rules.push(
    source_token_rule(
      "stub_frame_pool.h",
      [
        "#ifndef LOCKWIRE_NATIVE_STUB_FRAME_POOL_H", "#define LOCKWIRE_NATIVE_STUB_FRAME_POOL_H",
        "#define LOCKWIRE_NATIVE_FRAME_POOL_MAX_POOLS 16", "#define LOCKWIRE_NATIVE_FRAME_POOL_MAX_CAPACITY 128",
        "#define LOCKWIRE_NATIVE_FRAME_POOL_MAX_FRAME_SIZE 65536",
      ],
      ["MOONBIT_FFI_EXPORT"],
    ),
  )
  rules.push(
    source_token_rule(
      "stub_npcap_windows.h",
      [
        "#ifndef LOCKWIRE_NATIVE_STUB_NPCAP_WINDOWS_H", "#define LOCKWIRE_NATIVE_STUB_NPCAP_WINDOWS_H",
        "#define LOCKWIRE_MAX_NPCAP_LIVE_HANDLES 32", "#define LOCKWIRE_NPCAP_LIVE_MAX_FRAME_SIZE 65536",
        "#define LOCKWIRE_NPCAP_FILTER_MAX_EXPR_SIZE 4096",
      ],
      ["MOONBIT_FFI_EXPORT"],
    ),
  )
  rules.push(
    source_token_rule(
      "stub_raw_socket_linux.h",
      [
        "#ifndef LOCKWIRE_NATIVE_STUB_RAW_SOCKET_LINUX_H", "#define LOCKWIRE_NATIVE_STUB_RAW_SOCKET_LINUX_H",
        "#define LOCKWIRE_MAX_RAW_SOCKET_HANDLES 32", "#define LOCKWIRE_RAW_SOCKET_MAX_FRAME_SIZE 65535",
      ],
      ["MOONBIT_FFI_EXPORT"],
    ),
  )
  rules.push(
    source_token_rule(
      "stub_udp_windows.h",
      [
        "#ifndef LOCKWIRE_NATIVE_STUB_UDP_WINDOWS_H", "#define LOCKWIRE_NATIVE_STUB_UDP_WINDOWS_H",
        "#define LOCKWIRE_MAX_UDP_HANDLES 32", "#define LOCKWIRE_UDP_WSAETIMEDOUT 10060",
      ],
      ["MOONBIT_FFI_EXPORT"],
    ),
  )
  rules.push(
    source_token_rule(
      "stub_udp_posix.h",
      [
        "#ifndef LOCKWIRE_NATIVE_STUB_UDP_POSIX_H", "#define LOCKWIRE_NATIVE_STUB_UDP_POSIX_H",
        "#define LOCKWIRE_MAX_UDP_HANDLES 32",
      ],
      ["MOONBIT_FFI_EXPORT"],
    ),
  )
  rules.push(
    source_token_rule("stub_pcap_common.c", [], ["MOONBIT_FFI_EXPORT"]),
  )
  rules.push(
    source_token_rule("stub_frame_pool_common.c", [], ["MOONBIT_FFI_EXPORT"]),
  )
  rules.push(
    source_token_rule("stub_npcap_windows_common.c", [], ["MOONBIT_FFI_EXPORT"]),
  )
  rules.push(
    source_token_rule("stub_common_status.c", ["#include "], []),
  )
  rules.push(
    source_token_rule("stub_clock_windows.c", ["QueryPerformanceCounter"], []),
  )
  rules.push(
    source_token_rule(
      "stub_clock_posix.c",
      [
        "#if defined(CLOCK_MONOTONIC_RAW)", "#elif defined(CLOCK_MONOTONIC)", "clock_gettime",
      ],
      [],
    ),
  )
  rules.push(
    source_token_rule(
      "stub_clock_unsupported.c",
      ["high resolution clock unavailable"],
      [],
    ),
  )
  rules
}

///|
pub fn native_stub_expected_source_token_facts() -> Array[
  NativeStubSourceTokenFact,
] {
  let facts : Array[NativeStubSourceTokenFact] = []
  for rule in native_stub_source_token_rules() {
    for token in rule.required_tokens {
      facts.push(source_token_fact(rule.file, token, true))
    }
    for token in rule.forbidden_tokens {
      facts.push(source_token_fact(rule.file, token, false))
    }
  }
  facts
}

///|
pub fn native_stub_source_token_policy_report(
  actual : Array[NativeStubSourceTokenFact],
) -> NativeStubSourceTokenPolicyReport {
  native_stub_source_token_policy_report_from_rules(
    native_stub_source_token_rules(),
    actual,
  )
}

///|
pub fn native_stub_source_token_policy_report_from_rules(
  rules : Array[NativeStubSourceTokenRule],
  actual : Array[NativeStubSourceTokenFact],
) -> NativeStubSourceTokenPolicyReport {
  {
    rule_count: rules.length(),
    actual_file_count: count_actual_source_token_files(actual),
    missing_file_count: count_missing_source_token_files(rules, actual),
    extra_file_count: count_extra_source_token_files(rules, actual),
    required_token_count: count_required_source_tokens(rules),
    forbidden_token_count: count_forbidden_source_tokens(rules),
    missing_required_token_count: count_missing_required_source_tokens(
      rules, actual,
    ),
    present_forbidden_token_count: count_present_forbidden_source_tokens(
      rules, actual,
    ),
    unexpected_token_fact_count: count_unexpected_source_token_facts(
      rules, actual,
    ),
    duplicate_token_fact_count: count_duplicate_source_token_facts(actual),
    replaces_file_reading: false,
    replaces_token_extraction: false,
    live_io_evidence: false,
  }
}

///|
fn source_token_rule(
  file : String,
  required_tokens : Array[String],
  forbidden_tokens : Array[String],
) -> NativeStubSourceTokenRule {
  { file, required_tokens, forbidden_tokens }
}

///|
fn source_token_fact(
  file : String,
  token : String,
  present : Bool,
) -> NativeStubSourceTokenFact {
  { file, token, present }
}

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

///|
fn count_missing_source_token_files(
  rules : Array[NativeStubSourceTokenRule],
  actual : Array[NativeStubSourceTokenFact],
) -> Int {
  let mut count = 0
  for rule in rules {
    if !source_token_contains_file(actual, rule.file) {
      count += 1
    }
  }
  count
}

///|
fn count_extra_source_token_files(
  rules : Array[NativeStubSourceTokenRule],
  actual : Array[NativeStubSourceTokenFact],
) -> Int {
  let seen : Array[String] = []
  let mut count = 0
  for fact in actual {
    if !source_token_contains_string(seen, fact.file) {
      seen.push(fact.file)
      if !source_token_contains_rule_file(rules, fact.file) {
        count += 1
      }
    }
  }
  count
}

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

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

///|
fn count_missing_required_source_tokens(
  rules : Array[NativeStubSourceTokenRule],
  actual : Array[NativeStubSourceTokenFact],
) -> Int {
  let mut count = 0
  for rule in rules {
    for token in rule.required_tokens {
      if !source_token_contains_present_fact(actual, rule.file, token) {
        count += 1
      }
    }
  }
  count
}

///|
fn count_present_forbidden_source_tokens(
  rules : Array[NativeStubSourceTokenRule],
  actual : Array[NativeStubSourceTokenFact],
) -> Int {
  let mut count = 0
  for rule in rules {
    for token in rule.forbidden_tokens {
      if source_token_contains_present_fact(actual, rule.file, token) {
        count += 1
      }
    }
  }
  count
}

///|
fn count_unexpected_source_token_facts(
  rules : Array[NativeStubSourceTokenRule],
  actual : Array[NativeStubSourceTokenFact],
) -> Int {
  let mut count = 0
  for fact in actual {
    if !source_token_rule_allows_fact(rules, fact.file, fact.token) {
      count += 1
    }
  }
  count
}

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

///|
fn source_token_rule_allows_fact(
  rules : Array[NativeStubSourceTokenRule],
  file : String,
  token : String,
) -> Bool {
  for rule in rules {
    if rule.file == file &&
      (
        source_token_contains_string(rule.required_tokens, token) ||
        source_token_contains_string(rule.forbidden_tokens, token)
      ) {
      return true
    }
  }
  false
}

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

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

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

///|
fn source_token_contains_file_token(
  facts : Array[NativeStubSourceTokenFact],
  file : String,
  token : String,
) -> Bool {
  for got in facts {
    if got.file == file && got.token == token {
      return true
    }
  }
  false
}

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