// Deterministic serializer plus a validated generator: `serialize_security_txt` renders
// comments first, then fields in stored order with canonical names and LF endings.
// `SecurityTxtBuilder` builds typed documents; `build` validates, returning the first error.

///|
/// Serialize to canonical text: comments then fields in stored order, LF line endings.
pub fn serialize_security_txt(document : SecurityTxt) -> String {
  let sb = StringBuilder::new(size_hint=document.byte_count() + 64)
  for comment in document.comments() {
    sb.write_string("#" + comment + "\n")
  }
  for entry in document.entries() {
    sb.write_string(entry.field.name() + ": " + entry.field.value() + "\n")
  }
  sb.to_string()
}

///|
/// Builder for generating valid security.txt documents.
pub struct SecurityTxtBuilder {
  contacts : Array[String]
  mut expires : DateTime?
  mut canonical : String?
  mut encryption : String?
  acknowledgments : Array[String]
  mut policy : String?
  mut hiring : String?
  mut preferred_languages : Array[String]?
  extensions : Array[ExtensionField]
  comments : Array[String]
}

///|
/// Start a new builder.
pub fn new_builder() -> SecurityTxtBuilder {
  {
    contacts: [],
    expires: None,
    canonical: None,
    encryption: None,
    acknowledgments: [],
    policy: None,
    hiring: None,
    preferred_languages: None,
    extensions: [],
    comments: [],
  }
}

///|
/// Add a `Contact` field. Multiple calls preserve call order.
pub fn SecurityTxtBuilder::contact(
  self : SecurityTxtBuilder,
  uri : String,
) -> SecurityTxtBuilder {
  self.contacts.push(uri)
  self
}

///|
/// Set the `Expires` field from a `DateTime` (formatted as RFC 3339).
pub fn SecurityTxtBuilder::expires(
  self : SecurityTxtBuilder,
  expires : DateTime,
) -> SecurityTxtBuilder {
  self.expires = Some(expires)
  self
}

///|
/// Set the `Canonical` field.
pub fn SecurityTxtBuilder::canonical(
  self : SecurityTxtBuilder,
  uri : String,
) -> SecurityTxtBuilder {
  self.canonical = Some(uri)
  self
}

///|
/// Set the `Encryption` field.
pub fn SecurityTxtBuilder::encryption(
  self : SecurityTxtBuilder,
  uri : String,
) -> SecurityTxtBuilder {
  self.encryption = Some(uri)
  self
}

///|
/// Add an `Acknowledgments` field.
pub fn SecurityTxtBuilder::acknowledgments(
  self : SecurityTxtBuilder,
  uri : String,
) -> SecurityTxtBuilder {
  self.acknowledgments.push(uri)
  self
}

///|
/// Set the `Policy` field.
pub fn SecurityTxtBuilder::policy(
  self : SecurityTxtBuilder,
  uri : String,
) -> SecurityTxtBuilder {
  self.policy = Some(uri)
  self
}

///|
/// Set the `Hiring` field.
pub fn SecurityTxtBuilder::hiring(
  self : SecurityTxtBuilder,
  uri : String,
) -> SecurityTxtBuilder {
  self.hiring = Some(uri)
  self
}

///|
/// Set `Preferred-Languages`; tags are joined with `, `.
pub fn SecurityTxtBuilder::preferred_languages(
  self : SecurityTxtBuilder,
  tags : Array[String],
) -> SecurityTxtBuilder {
  self.preferred_languages = Some(tags)
  self
}

///|
/// Add an extension field.
pub fn SecurityTxtBuilder::extension(
  self : SecurityTxtBuilder,
  name : String,
  value : String,
) -> SecurityTxtBuilder {
  self.extensions.push(extension_field(name, value))
  self
}

///|
/// Add a comment line (without the leading `#`).
pub fn SecurityTxtBuilder::comment(
  self : SecurityTxtBuilder,
  text : String,
) -> SecurityTxtBuilder {
  self.comments.push(text)
  self
}

///|
/// Push an entry for `field` at `line` and return the next line number.
fn push_field(
  entries : Array[SecurityFieldEntry],
  field : SecurityField,
  line : Int,
) -> Int {
  entries.push(security_field_entry(field, line, 0))
  line + 1
}

///|
/// Push an optional entry (built by `make`) or leave `line` unchanged.
fn[T] push_opt(
  entries : Array[SecurityFieldEntry],
  opt : T?,
  line : Int,
  make : (T) -> SecurityField,
) -> Int {
  match opt {
    Some(v) => push_field(entries, make(v), line)
    None => line
  }
}

///|
/// Build and validate; fixed deterministic field order; errors are returned as-is.
pub fn SecurityTxtBuilder::build(
  self : SecurityTxtBuilder,
) -> Result[SecurityTxt, SecurityTxtError] {
  let entries : Array[SecurityFieldEntry] = []
  let mut line = 1
  for contact in self.contacts {
    line = push_field(entries, Contact(contact), line)
  }
  line = push_opt(entries, self.expires, line, fn(dt : DateTime) {
    Expires(dt.format_rfc3339())
  })
  line = push_opt(entries, self.canonical, line, fn(uri : String) {
    Canonical(uri)
  })
  line = push_opt(entries, self.encryption, line, fn(uri : String) {
    Encryption(uri)
  })
  for uri in self.acknowledgments {
    line = push_field(entries, Acknowledgments(uri), line)
  }
  line = push_opt(entries, self.policy, line, fn(uri : String) { Policy(uri) })
  line = push_opt(entries, self.hiring, line, fn(uri : String) { Hiring(uri) })
  line = push_opt(entries, self.preferred_languages, line, fn(tags) {
    PreferredLanguages(tags.join(", "))
  })
  for ext in self.extensions {
    line = push_field(entries, Extension(ext.name, ext.value), line)
  }
  let document = security_txt(entries, self.comments, Unsigned, [], line - 1, 0)
  let serialized = serialize_security_txt(document)
  let full = security_txt(
    entries,
    self.comments,
    Unsigned,
    [],
    line - 1,
    utf8_byte_len(serialized),
  )
  match validate(full) {
    Ok(_) =>
      // Validation intentionally ignores extension syntax and comments; a final
      // parser round-trip guarantees the generator can only emit legal text.
      match parse_security_txt(serialized) {
        Ok(_) => Ok(full)
        Err(err) => Err(err)
      }
    Err(err) => Err(err)
  }
}