///|
pub(all) enum TrustLevel {
  Full
  Partial
  Untrusted
} derive(Eq)

///|
pub fn TrustLevel::to_string(self : TrustLevel) -> String {
  match self {
    Full => "Full"
    Partial => "Partial"
    Untrusted => "Untrusted"
  }
}

///|
pub impl Show for TrustLevel with fn output(self : TrustLevel, logger : &Logger) -> Unit {
  match self {
    Full => logger.write_string("Full")
    Partial => logger.write_string("Partial")
    Untrusted => logger.write_string("Untrusted")
  }
}

///|
pub(all) struct TrustedKey {
  key_id : String
  public_key : String
  owner : String
  trust_level : TrustLevel
  added_at : String
}

///|
pub(all) struct TrustStore {
  mut keys : Array[TrustedKey]
}

///|
pub fn TrustStore::new() -> TrustStore {
  TrustStore::{ keys: [] }
}

///|
pub fn TrustStore::add_key(
  self : TrustStore,
  key_id : String,
  public_key : String,
  owner : String,
  trust_level : TrustLevel,
) -> Bool {
  if !validate_public_key_format(public_key) {
    return false
  }
  for i = 0; i < self.keys.length(); i = i + 1 {
    if self.keys[i].key_id == key_id {
      return false
    }
  }
  self.keys.push(TrustedKey::{
    key_id,
    public_key,
    owner,
    trust_level,
    added_at: "2026-01-01",
  })
  true
}

///|
pub fn TrustStore::remove_key(self : TrustStore, key_id : String) -> Bool {
  let new_keys : Array[TrustedKey] = []
  let mut found = false
  for i = 0; i < self.keys.length(); i = i + 1 {
    if self.keys[i].key_id == key_id {
      found = true
    } else {
      new_keys.push(self.keys[i])
    }
  }
  if found {
    self.keys = new_keys
  }
  found
}

///|
pub fn TrustStore::get_key(self : TrustStore, key_id : String) -> TrustedKey? {
  for i = 0; i < self.keys.length(); i = i + 1 {
    if self.keys[i].key_id == key_id {
      return Some(self.keys[i])
    }
  }
  None
}

///|
pub fn TrustStore::is_trusted(self : TrustStore, key_id : String) -> Bool {
  match self.get_key(key_id) {
    Some(k) => k.trust_level != TrustLevel::Untrusted
    None => false
  }
}

///|
pub fn TrustStore::find_by_owner(
  self : TrustStore,
  owner : String,
) -> Array[TrustedKey] {
  let result : Array[TrustedKey] = []
  for i = 0; i < self.keys.length(); i = i + 1 {
    if self.keys[i].owner == owner {
      result.push(self.keys[i])
    }
  }
  result
}

///|
pub fn TrustStore::key_count(self : TrustStore) -> Int {
  self.keys.length()
}

///|
pub fn TrustStore::list_all(self : TrustStore) -> Array[TrustedKey] {
  let result : Array[TrustedKey] = []
  for i = 0; i < self.keys.length(); i = i + 1 {
    result.push(self.keys[i])
  }
  result
}

///|
/// Replace the contents of the store. Used by `load_from_json` to restore
/// from persistence and by tests to set up fixtures.
pub fn TrustStore::replace_all(
  self : TrustStore,
  keys : Array[TrustedKey],
) -> Unit {
  self.keys = keys
}

///|
/// Persist this store to a JSON file at `path`, truncating any existing
/// file. Returns `false` when the write fails (unwritable path, I/O error).
/// This is the cross-process persistence entry point: one process writes,
/// another process can `load_from_file` the same path and get the keys.
pub fn TrustStore::save_to_file(self : TrustStore, path : String) -> Bool {
  @fs.write_string_to_file(path, trust_store_to_json(self)) catch {
    _ => return false
  }
  true
}

///|
/// Load a trust store from a JSON file written by `save_to_file`. Returns
/// `None` when the file is missing, unreadable, or contains a malformed
/// trust-store document.
pub fn load_store_from_file(path : String) -> TrustStore? {
  let content = @fs.read_file_to_string(path) catch { _ => return None }
  trust_store_from_json(content)
}

///|
pub fn validate_public_key_format(public_key : String) -> Bool {
  if public_key.length() != 64 {
    return false
  }
  for i = 0; i < public_key.length(); i = i + 1 {
    let c = public_key[i]
    let valid = (c >= 48 && c <= 57) ||
      (c >= 97 && c <= 102) ||
      (c >= 65 && c <= 70)
    if !valid {
      return false
    }
  }
  true
}

///|
/// Normalize an Ed25519 public key to lower-case hex, accepting mixed-case
/// input from PEM files or operator copy-paste. Returns `None` when the
/// input has the wrong shape.
pub fn normalize_public_key(public_key : String) -> String? {
  if public_key.length() != 64 {
    return None
  }
  let buf = StringBuilder::new(size_hint=64)
  for i = 0; i < 64; i = i + 1 {
    let c = public_key[i]
    let ok = (c >= 48 && c <= 57) ||
      (c >= 97 && c <= 102) ||
      (c >= 65 && c <= 70)
    if !ok {
      return None
    }
    if c >= 65 && c <= 70 {
      // A-F -> a-f
      buf.write_char((c + 32).unsafe_to_char())
    } else {
      buf.write_char(c.unsafe_to_char())
    }
  }
  Some(buf.to_string())
}

///|
/// Stable JSON encoding used by both the CLI and the file persistence path.
/// Kept in one place so the wire format stays consistent across consumers.
pub fn trust_store_to_json(store : TrustStore) -> String {
  let mut result = "{\"version\":\"1\",\"keys\":["
  for i = 0; i < store.keys.length(); i = i + 1 {
    if i > 0 {
      result = result + ","
    }
    let k = store.keys[i]
    result = result + "{\"key_id\":\"" + k.key_id + "\","
    result = result + "\"public_key\":\"" + k.public_key + "\","
    result = result + "\"owner\":\"" + k.owner + "\","
    result = result +
      "\"trust_level\":\"" +
      trust_level_str(k.trust_level) +
      "\","
    result = result + "\"added_at\":\"" + k.added_at + "\"}"
  }
  result + "]}"
}

///|
/// Parse a `trust_store_to_json` document into a fresh store. Returns
/// `None` when the document is malformed so the caller can surface a
/// useful error rather than a partial store.
pub fn trust_store_from_json(json : String) -> TrustStore? {
  let store = TrustStore::new()
  // Find the version field; default to "1" when omitted (legacy format).
  let version = extract_string_field(json, "version")
  let _ = version // reserved for future migration
  let arr_body = match extract_array(json, "keys") {
    Some(s) => s
    None => return None
  }
  let entries = split_top_level_objects(arr_body)
  for i = 0; i < entries.length(); i = i + 1 {
    let entry = entries[i]
    let key_id = match extract_string_field(entry, "key_id") {
      Some(s) => s
      None => return None
    }
    let public_key = match extract_string_field(entry, "public_key") {
      Some(s) => s
      None => return None
    }
    let owner = match extract_string_field(entry, "owner") {
      Some(s) => s
      None => return None
    }
    let level_str = match extract_string_field(entry, "trust_level") {
      Some(s) => s
      None => "full"
    }
    let trust_level = trust_level_from_str(level_str)
    let added_at = match extract_string_field(entry, "added_at") {
      Some(s) => s
      None => "2026-01-01"
    }
    // Skip entries whose public key isn't 64-char hex — they're invalid.
    if !validate_public_key_format(public_key) {
      return None
    }
    // Silently drop duplicate key_ids when re-loading.
    let already = store.get_key(key_id)
    if already is Some(_) {
      continue
    }
    store.keys.push(TrustedKey::{
      key_id,
      public_key,
      owner,
      trust_level,
      added_at,
    })
  }
  Some(store)
}

///|
fn trust_level_str(level : TrustLevel) -> String {
  match level {
    Full => "full"
    Partial => "partial"
    Untrusted => "untrusted"
  }
}

///|
fn trust_level_from_str(s : String) -> TrustLevel {
  if s == "full" {
    TrustLevel::Full
  } else if s == "partial" {
    TrustLevel::Partial
  } else {
    TrustLevel::Untrusted
  }
}

///|
/// PEM encode an Ed25519 public key for storage in `.pem` files.
/// Format follows RFC 7468 section 13 ("PUBLIC KEY") which is the same
/// shape libsodium / age / OpenSSH use for raw Ed25519 keys.
pub fn public_key_to_pem(public_key : String) -> String {
  let prefix = "-----BEGIN MOONGUARD PUBLIC KEY-----\n"
  let suffix = "-----END MOONGUARD PUBLIC KEY-----\n"
  let buf = StringBuilder::new(size_hint=public_key.length() * 2)
  buf.write_string(prefix)
  // Emit 64-char chunks until fewer than 64 remain, then flush the tail.
  let full_chunks = public_key.length() / 64
  let mut chunk_idx = 0
  while chunk_idx < full_chunks {
    // Each chunk is built from 64 individual chars to avoid Result-typed
    // slicing APIs.
    let line = StringBuilder::new(size_hint=64)
    let mut k = 0
    while k < 64 {
      line.write_char(public_key[chunk_idx * 64 + k].unsafe_to_char())
      k = k + 1
    }
    buf.write_string(line.to_string())
    buf.write_char('\n')
    chunk_idx = chunk_idx + 1
  }
  if full_chunks * 64 < public_key.length() {
    let tail = StringBuilder::new(size_hint=public_key.length())
    let mut k = full_chunks * 64
    while k < public_key.length() {
      tail.write_char(public_key[k].unsafe_to_char())
      k = k + 1
    }
    buf.write_string(tail.to_string())
    buf.write_char('\n')
  }
  buf.write_string(suffix)
  buf.to_string()
}

///|
/// Inverse of `public_key_to_pem`. Accepts both upper- and lower-case hex
/// inside the PEM envelope. Returns `None` when the input doesn't look
/// like a MoonGuard public key PEM.
pub fn public_key_from_pem(pem : String) -> String? {
  let start = "-----BEGIN MOONGUARD PUBLIC KEY-----"
  let end = "-----END MOONGUARD PUBLIC KEY-----"
  if !contains_substring(pem, start) {
    return None
  }
  if !contains_substring(pem, end) {
    return None
  }
  // Find the body between the header and footer lines.
  let mut start_idx = 0
  let mut i = 0
  while i < pem.length() - start.length() {
    if match_substring_at(pem, start, i) {
      start_idx = i + start.length()
      break
    }
    i = i + 1
  }
  if start_idx == 0 {
    return None
  }
  // Skip the newline right after the header line.
  if start_idx < pem.length() && pem[start_idx] == '\n' {
    start_idx = start_idx + 1
  }
  let mut end_idx = pem.length()
  i = start_idx
  while i < pem.length() - end.length() {
    if match_substring_at(pem, end, i) {
      end_idx = i
      break
    }
    i = i + 1
  }
  let body = @manifest.slice_chars_(pem, start_idx, end_idx)
  // Strip whitespace and dashes to isolate the hex digits.
  let buf = StringBuilder::new(size_hint=64)
  i = 0
  while i < body.length() {
    let c = body[i]
    let is_hex = (c >= 48 && c <= 57) ||
      (c >= 65 && c <= 70) ||
      (c >= 97 && c <= 102)
    if is_hex {
      buf.write_char(c.unsafe_to_char())
    }
    i = i + 1
  }
  let hex = buf.to_string()
  if hex.length() != 64 {
    return None
  }
  normalize_public_key(hex)
}

///|
fn contains_substring(haystack : String, needle : String) -> Bool {
  if needle.length() > haystack.length() {
    return false
  }
  let mut i = 0
  while i <= haystack.length() - needle.length() {
    if match_substring_at(haystack, needle, i) {
      return true
    }
    i = i + 1
  }
  false
}

///|
fn match_substring_at(haystack : String, needle : String, offset : Int) -> Bool {
  if offset + needle.length() > haystack.length() {
    return false
  }
  let mut i = 0
  while i < needle.length() {
    if haystack[offset + i] != needle[i] {
      return false
    }
    i = i + 1
  }
  true
}

///|
/// Pull a quoted string field out of a flat JSON object. Returns `None`
/// when the field is missing or the surrounding braces don't match. Only
/// good enough for the trust-store schema, which is small and produced
/// by `trust_store_to_json`.
fn extract_string_field(json : String, field : String) -> String? {
  let needle = "\"" + field + "\":\""
  let idx = find_substring(json, needle)
  if idx < 0 {
    return None
  }
  let value_start = idx + needle.length()
  let mut i = value_start
  while i < json.length() && json[i] != '"' {
    i = i + 1
  }
  if i >= json.length() {
    return None
  }
  Some(@manifest.slice_chars_(json, value_start, i))
}

///|
fn find_substring(haystack : String, needle : String) -> Int {
  if needle.length() == 0 {
    return 0
  }
  if needle.length() > haystack.length() {
    return -1
  }
  let mut i = 0
  while i <= haystack.length() - needle.length() {
    if match_substring_at(haystack, needle, i) {
      return i
    }
    i = i + 1
  }
  -1
}

///|
/// Pull the contents of the `"keys":[ ... ]` array out of a trust-store
/// document so we can split it into individual object entries.
fn extract_array(json : String, field : String) -> String? {
  let needle = "\"" + field + "\":["
  let idx = find_substring(json, needle)
  if idx < 0 {
    return None
  }
  let value_start = idx + needle.length()
  // Walk forward, counting brace depth, to find the matching `]`.
  let mut depth = 1
  let mut i = value_start
  while i < json.length() && depth > 0 {
    let c = json[i]
    if c == '[' {
      depth = depth + 1
    } else if c == ']' {
      depth = depth - 1
    }
    i = i + 1
  }
  if depth != 0 {
    return None
  }
  Some(@manifest.slice_chars_(json, value_start, i - 1))
}

///|
/// Split an array body like `{...},{...},{...}` into individual objects.
/// Respects brace nesting so a string field containing `,` or `{` doesn't
/// break the split.
fn split_top_level_objects(body : String) -> Array[String] {
  let result : Array[String] = []
  let mut i = 0
  let len = body.length()
  while i < len {
    // Skip whitespace and commas between entries.
    while i < len {
      let c = body[i]
      if c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == ',' {
        i = i + 1
      } else {
        break
      }
    }
    if i >= len {
      break
    }
    if body[i] != '{' {
      // Malformed entry — bail out.
      break
    }
    let start = i
    let mut depth = 1
    i = i + 1
    while i < len && depth > 0 {
      let c = body[i]
      if c == '{' {
        depth = depth + 1
      } else if c == '}' {
        depth = depth - 1
      }
      i = i + 1
    }
    if depth != 0 {
      break
    }
    result.push(@manifest.slice_chars_(body, start, i))
  }
  result
}