// Core semantic model for RFC 9116 security.txt documents. Field values are stored with the
// `Name:` syntax stripped; typed accessors parse on demand so the parser stays syntactic and
// the validator keeps sole ownership of RFC field constraints.

///|
/// A parsed field: standard fields carry raw values; unknown fields become `Extension`.
pub(all) enum SecurityField {
  Contact(String)
  Expires(String)
  Canonical(String)
  Encryption(String)
  Acknowledgments(String)
  Policy(String)
  Hiring(String)
  PreferredLanguages(String)
  Extension(String, String)
} derive(Eq, Debug)

///|
/// Signature state: the OpenPGP envelope is extracted but never verified.
pub(all) enum SignatureState {
  Unsigned
  SignedUnverified
} derive(Eq, Debug)

///|
/// An ordered field record with its position in the source file.
pub struct SecurityFieldEntry {
  field : SecurityField
  line : Int
  byte_offset : Int
} derive(Eq, Debug)

///|
/// An unknown (extension) field: RFC 9116 fields are extensible.
pub struct ExtensionField {
  name : String
  value : String
} derive(Eq, Debug)

///|
/// A parsed security.txt document; field order is preserved.
pub struct SecurityTxt {
  entries : Array[SecurityFieldEntry]
  comments : Array[String]
  signature : SignatureState
  armor_headers : Array[String]
  line_count : Int
  byte_count : Int
} derive(Debug)

///|
/// Construct a document. Prefer `parse_security_txt` or the generator; for tests.
pub fn security_txt(
  entries : Array[SecurityFieldEntry],
  comments : Array[String],
  signature : SignatureState,
  armor_headers : Array[String],
  line_count : Int,
  byte_count : Int,
) -> SecurityTxt {
  { entries, comments, signature, armor_headers, line_count, byte_count }
}

///|
/// Construct an ordered field record.
pub fn security_field_entry(
  field : SecurityField,
  line : Int,
  byte_offset : Int,
) -> SecurityFieldEntry {
  { field, line, byte_offset }
}

///|
/// Construct an extension field record.
pub fn extension_field(name : String, value : String) -> ExtensionField {
  { name, value }
}

///|
/// All fields in original document order.
pub fn SecurityTxt::fields(self : SecurityTxt) -> Array[SecurityField] {
  let out : Array[SecurityField] = []
  for entry in self.entries {
    out.push(entry.field)
  }
  out
}

///|
/// Ordered field records with positions.
pub fn SecurityTxt::entries(self : SecurityTxt) -> Array[SecurityFieldEntry] {
  self.entries
}

///|
/// All `Contact` values in original order.
pub fn SecurityTxt::contacts(self : SecurityTxt) -> Array[String] {
  let out : Array[String] = []
  for entry in self.entries {
    match entry.field {
      Contact(value) => out.push(value)
      _ => ()
    }
  }
  out
}

///|
/// The preferred contact: the first `Contact`, exactly as written (no scheme ranking).
pub fn SecurityTxt::preferred_contact(self : SecurityTxt) -> String? {
  for entry in self.entries {
    match entry.field {
      Contact(value) => return Some(value)
      _ => ()
    }
  }
  None
}

///|
/// Raw `Expires` value, when present.
pub fn SecurityTxt::expires_value(self : SecurityTxt) -> String? {
  for entry in self.entries {
    match entry.field {
      Expires(value) => return Some(value)
      _ => ()
    }
  }
  None
}

///|
/// Parsed `Expires` as a `DateTime`; fails SecurityTxtError when absent or malformed.
pub fn SecurityTxt::expires(
  self : SecurityTxt,
) -> Result[DateTime, SecurityTxtError] {
  for entry in self.entries {
    match entry.field {
      Expires(value) =>
        match parse_rfc3339(value) {
          Ok(dt) => return Ok(dt)
          Err(err) =>
            return Err(
              security_txt_error(
                err.stage(),
                err.kind(),
                entry.line,
                entry.byte_offset + 1 + err.column(),
                entry.byte_offset + 1 + err.byte_offset(),
                err.message(),
              ),
            )
        }
      _ => ()
    }
  }
  Err(
    security_txt_error(
      Validation,
      MissingExpires,
      0,
      0,
      -1,
      "document has no Expires field",
    ),
  )
}

///|
/// Raw `Canonical` value, when present.
pub fn SecurityTxt::canonical(self : SecurityTxt) -> String? {
  for entry in self.entries {
    match entry.field {
      Canonical(value) => return Some(value)
      _ => ()
    }
  }
  None
}

///|
/// All `Canonical` values in original order.
pub fn SecurityTxt::canonicals(self : SecurityTxt) -> Array[String] {
  let out : Array[String] = []
  for entry in self.entries {
    match entry.field {
      Canonical(value) => out.push(value)
      _ => ()
    }
  }
  out
}

///|
/// Raw `Encryption` value, when present.
pub fn SecurityTxt::encryption(self : SecurityTxt) -> String? {
  for entry in self.entries {
    match entry.field {
      Encryption(value) => return Some(value)
      _ => ()
    }
  }
  None
}

///|
/// All `Acknowledgments` values in original order.
pub fn SecurityTxt::acknowledgments(self : SecurityTxt) -> Array[String] {
  let out : Array[String] = []
  for entry in self.entries {
    match entry.field {
      Acknowledgments(value) => out.push(value)
      _ => ()
    }
  }
  out
}

///|
/// Raw `Policy` value, when present.
pub fn SecurityTxt::policy(self : SecurityTxt) -> String? {
  for entry in self.entries {
    match entry.field {
      Policy(value) => return Some(value)
      _ => ()
    }
  }
  None
}

///|
/// Raw `Hiring` value, when present.
pub fn SecurityTxt::hiring(self : SecurityTxt) -> String? {
  for entry in self.entries {
    match entry.field {
      Hiring(value) => return Some(value)
      _ => ()
    }
  }
  None
}

///|
/// Raw `Preferred-Languages` value, when present.
pub fn SecurityTxt::preferred_languages_value(self : SecurityTxt) -> String? {
  for entry in self.entries {
    match entry.field {
      PreferredLanguages(value) => return Some(value)
      _ => ()
    }
  }
  None
}

///|
/// `Preferred-Languages` split into trimmed tags, in listed order.
pub fn SecurityTxt::preferred_languages(self : SecurityTxt) -> Array[String] {
  match self.preferred_languages_value() {
    None => []
    Some(value) => split_preferred_languages(value)
  }
}

///|
/// All extension fields in original order.
pub fn SecurityTxt::extensions(self : SecurityTxt) -> Array[ExtensionField] {
  let out : Array[ExtensionField] = []
  for entry in self.entries {
    match entry.field {
      Extension(name, value) => out.push(extension_field(name, value))
      _ => ()
    }
  }
  out
}

///|
/// Comment text lines (without the leading `#`), in original order.
pub fn SecurityTxt::comments(self : SecurityTxt) -> Array[String] {
  self.comments
}

///|
/// True when the input carried an OpenPGP signed envelope.
pub fn SecurityTxt::is_signed(self : SecurityTxt) -> Bool {
  self.signature == SignedUnverified
}

///|
/// Signature state of the document.
pub fn SecurityTxt::signature_state(self : SecurityTxt) -> SignatureState {
  self.signature
}

///|
/// OpenPGP armor headers seen in the signed envelope (e.g. `Hash: SHA256`).
pub fn SecurityTxt::armor_headers(self : SecurityTxt) -> Array[String] {
  self.armor_headers
}

///|
/// Number of physical lines in the original input.
pub fn SecurityTxt::line_count(self : SecurityTxt) -> Int {
  self.line_count
}

///|
/// Size of the original input in bytes.
pub fn SecurityTxt::byte_count(self : SecurityTxt) -> Int {
  self.byte_count
}

///|
/// Total number of fields, standard and extension.
pub fn SecurityTxt::field_count(self : SecurityTxt) -> Int {
  self.entries.length()
}

///|
/// Number of standard RFC 9116 fields.
pub fn SecurityTxt::standard_field_count(self : SecurityTxt) -> Int {
  let mut count = 0
  for entry in self.entries {
    if entry.field.is_standard() {
      count += 1
    }
  }
  count
}

///|
/// Number of extension fields.
pub fn SecurityTxt::extension_field_count(self : SecurityTxt) -> Int {
  self.field_count() - self.standard_field_count()
}

///|
/// Canonical field name for this field; for extensions, the name as written.
pub fn SecurityField::name(self : SecurityField) -> String {
  match self {
    Contact(_) => "Contact"
    Expires(_) => "Expires"
    Canonical(_) => "Canonical"
    Encryption(_) => "Encryption"
    Acknowledgments(_) => "Acknowledgments"
    Policy(_) => "Policy"
    Hiring(_) => "Hiring"
    PreferredLanguages(_) => "Preferred-Languages"
    Extension(name, _) => name
  }
}

///|
/// Raw value string for this field.
pub fn SecurityField::value(self : SecurityField) -> String {
  match self {
    Contact(v) | Expires(v) | Canonical(v) | Encryption(v) => v
    Acknowledgments(v) | Policy(v) | Hiring(v) | PreferredLanguages(v) => v
    Extension(_, v) => v
  }
}

///|
/// True for the nine standard RFC 9116 fields.
pub fn SecurityField::is_standard(self : SecurityField) -> Bool {
  match self {
    Extension(_, _) => false
    _ => true
  }
}

///|
/// True for fields that MUST NOT appear more than once per RFC 9116.
pub fn SecurityField::is_singleton(self : SecurityField) -> Bool {
  match self {
    Expires(_) | PreferredLanguages(_) => true
    _ => false
  }
}

///|
/// Library version string, used by the CLI's `--version`.
pub fn library_version() -> String {
  "0.1.0-dev"
}