// L1 fragment builders: the byte fragments `docx annotate` splices into
// EXISTING documents. Per the L0 locked rule every fragment is
// namespace-SELF-CONTAINED — each top-level element declares its own
// xmlns:w — so correctness never depends on the destination document's
// bindings (which may be hostile or absent).

///|
/// The two anchor fragments for comment `id`: the range start (spliced
/// at the from-paragraph's content_start) and the range end plus the
/// reference run (spliced at the to-paragraph's close_tag_start) — the
/// same canonical shape the K1 writer emits, in self-contained form.
pub fn comment_anchor_fragments(
  id~ : String,
  wordprocessing_namespace? : String = WORDPROCESSINGML_NAMESPACE,
) -> (String, String) {
  let namespaces = { "w": wordprocessing_namespace }
  let start = @xml.write_xml_fragment(
    @xml.xml_element("w:commentRangeStart", attributes={ "w:id": id }),
    namespaces~,
  )
  let end = @xml.write_xml_fragment(
      @xml.xml_element("w:commentRangeEnd", attributes={ "w:id": id }),
      namespaces~,
    ) +
    @xml.write_xml_fragment(
      @xml.xml_element("w:r", children=[
        XmlElement(
          @xml.xml_element("w:commentReference", attributes={ "w:id": id }),
        ),
      ]),
      namespaces~,
    )
  (start, end)
}

///|
/// The `w:comment` definition fragment for `spec` (whose anchored
/// range is ignored here — the splice layer owns placement). The body
/// is re-validated (the arrays stay caller-mutable) and serialized
/// through the same body writers as the packaging path, under the same
/// fail-closed guard against relationship-bearing content.
pub fn comment_definition_fragment(
  spec : CommentSpec,
  id~ : String,
  para_ids? : Array[String] = [],
  max_output_bytes? : Int = 8 * 1024 * 1024,
  wordprocessing_namespace? : String = WORDPROCESSINGML_NAMESPACE,
) -> String raise DocxError {
  check_comment_body_for_fragment(spec.body, max_output_bytes)
  check_attribute_value("the comment id", id)
  if para_ids.length() > 0 && para_ids.length() != spec.body.length() {
    raise Unsupported(
      message="paraId stamping needs exactly one id per body paragraph",
    )
  }
  let ctx = WriteContext::{
    used_styles: Set([]),
    uses_lists: false,
    next_relationship: 3,
    document_relationships: [],
    media: [],
    footnote_count: 0,
    endnote_count: 0,
  }
  let children : Array[XmlNode] = []
  for index, paragraph in spec.body {
    let element = write_block(paragraph, ctx)
    if para_ids.length() > 0 {
      element.attributes["w14:paraId"] = para_ids[index]
    }
    children.push(XmlElement(element))
  }
  if ctx.document_relationships.length() > 0 || ctx.media.length() > 0 {
    raise Unsupported(
      message="comment bodies are plain content: hyperlinks and images cannot be serialized into an annotate fragment",
    )
  }
  // Styles and list numbering resolve against the DESTINATION
  // document's parts, which annotate never rewrites — fail closed
  // rather than referencing styles the document may not define.
  if ctx.used_styles.length() > 0 || ctx.uses_lists {
    raise Unsupported(
      message="annotate comment bodies cannot use paragraph styles or lists (they would reference style and numbering definitions the existing document may not have)",
    )
  }
  let attributes : Map[String, String] = { "w:id": id, "w:author": spec.author }
  match spec.initials {
    Some(initials) => attributes["w:initials"] = initials
    None => ()
  }
  match spec.date {
    Some(date) => attributes["w:date"] = date
    None => ()
  }
  let namespaces : Map[String, String] = { "w": wordprocessing_namespace }
  if para_ids.length() > 0 {
    namespaces["w14"] = W14_NAMESPACE
  }
  @xml.write_xml_fragment_limited(
    @xml.xml_element("w:comment", attributes~, children~),
    max_output_bytes~,
    namespaces~,
  )
}

///|
/// Relationship types of annotation SIDECARS this tooling cannot keep
/// consistent when mutating comments (Word's modern comment identity
/// and people parts). Per the L0 matrix rule these are detected by
/// RELATIONSHIP TYPE from the main part — their presence makes
/// mutating commands fail closed. commentsExtended is NOT here: the
/// add operation never touches it (matrix row), and L2 owns it.
pub fn DocxAnnotatedResult::annotation_sidecars(
  self : DocxAnnotatedResult,
) -> Array[String] {
  self.sidecar_types.copy()
}