// Record key -- the last path segment of an AT-URI, naming one record within
// one collection of one repository.
//
// https://atproto.com/specs/record-key
// Ported from @atproto/syntax packages/syntax/src/recordkey.ts.
//
// Most record keys in practice are TIDs (`Tid` below), which sort
// chronologically, or the literal `self` for singleton records like
// `app.bsky.actor.profile`. But the syntax is wider than either, and a client
// that assumed otherwise would fail on perfectly ordinary repositories.
//
// Record keys are CASE-SENSITIVE. Unlike handles, `Self` and `self` are two
// different records, so nothing here normalizes.

///|
const RECORD_KEY_MAX_LENGTH : Int = 512

///|
/// A syntactically valid record key.
pub struct RecordKey(String) derive(Eq, Debug)

///|
pub impl Show for RecordKey with fn output(self, logger) {
  logger.write_string(self.0)
}

///|
pub fn RecordKey::to_string(self : Self) -> String {
  self.0
}

///|
pub fn RecordKey::unchecked(rkey : String) -> RecordKey {
  RecordKey(rkey)
}

///|
pub fn RecordKey::is_valid(rkey : String) -> Bool {
  try {
    RecordKey::parse(rkey) |> ignore
    true
  } catch {
    _ => false
  }
}

///|
/// The order matters for the message, and for one substantive reason: `.` and
/// `..` both satisfy the character set, so checking the set first would report
/// them as a syntax failure rather than as the reserved path components they
/// are. They are excluded because a record key is also a path segment in the
/// repository's Merkle tree.
pub fn RecordKey::parse(rkey : String) -> RecordKey raise SyntaxError {
  fn bad(reason : String) -> SyntaxError {
    SyntaxError(kind=RecordKey, input=rkey, reason~)
  }

  guard rkey.length() >= 1 && rkey.length() <= RECORD_KEY_MAX_LENGTH else {
    raise bad("record key must be 1 to \{RECORD_KEY_MAX_LENGTH} characters")
  }
  guard rkey != "." && rkey != ".." else {
    raise bad("record key can not be \".\" or \"..\"")
  }
  guard all_chars(rkey, is_record_key_char) else {
    raise bad("record key syntax not valid (regex)")
  }
  RecordKey(rkey)
}

///|
/// `[a-zA-Z0-9_~.:-]`. Every one of these is safe both as an MST path component
/// and as an unescaped RFC 3986 path segment, which is the constraint the set
/// was chosen to satisfy.
fn is_record_key_char(c : Char) -> Bool {
  is_ascii_alnum(c) || c == '_' || c == '~' || c == '.' || c == ':' || c == '-'
}