// PR 2 of the reader unification (#434): the output carrier and the reader IR.
//
// The body reader's recursive component is one strongly connected component
// through `read_children`'s return type. Every producer builds `New`
// reader-IR nodes with provenance, transforms rewrite the IR in place, and
// erasing to `DocumentElement` happens exactly once, in
// `reader_items_to_elements` -- the reader itself never erases.

///|
/// Which atom a projected text value came from. The projection's
/// contribution kinds, one level earlier.
priv enum ReaderProjectedTextKind {
  FromText
  FromTab
  FromNoBreakHyphen
  FromSoftHyphen
  FromSymbol
}

///|
/// What one reader decision contributed to the visible story. Emitted at the
/// same decision sites that produce or suppress semantic nodes, after every
/// transform has run: a text atom the checkbox transform replaced carries
/// `Suppressed` + `ProjectsVisibleNonText`, not its original `ProjectsText`.
/// The old projection's `TransparentSeam` maps to `TransparentBoundary`; its
/// `HardBarrier` conflated the last two kinds, which this stream separates.
priv enum ReaderEffect {
  ProjectsText(
    source~ : ReaderNode,
    kind~ : ReaderProjectedTextKind,
    value~ : String
  )
  ProjectsVisibleNonText(source~ : ReaderNode)
  TransparentBoundary(source~ : ReaderNode)
  Suppressed(source~ : ReaderNode)
}

///|
/// One item of reader output while the migration is in flight.
priv enum ReaderItem {
  /// A node built through the reader IR.
  New(ReaderTree)
  /// An effect with no surviving semantic output.
  Trace(ReaderEffect)
}

///|
/// A reader-built node. `source` is the reader decision site; PR 4 joins a
/// `SourceElementId` into `ReaderNode` itself, so producers -- which already
/// pass their input node here -- are never touched again.
priv struct ReaderTree {
  source : ReaderNode
  /// What this node's decision site contributed to the story, in order,
  /// ahead of the children's own effects.
  local_effects : Array[ReaderEffect]
  shape : ReaderTreeShape
}

///|
/// The reachable output shapes. Two deliberate omissions keep impossible
/// reader states unrepresentable: no `ReaderDocument` (assembly stays the
/// erase boundary) and no `ReaderField` (authoring-only; this reader never
/// produces it).
#warnings("-unused_field")
priv struct ReaderParagraphInput {
  /// The physical `w:p` element itself -- a deleted prefix or the
  /// surviving terminal paragraph.
  paragraph : ReaderNode
  /// Its DIRECT child elements in order: the field-carrier sequence the
  /// projection's field classification observes per physical paragraph.
  carriers : Array[ReaderNode]
}

///|
priv enum ReaderTreeShape {
  /// Deleted paragraph-mark prefixes first, terminal paragraph last, each
  /// with its own ordered carriers.
  ReaderParagraph(
    inputs~ : Array[ReaderParagraphInput],
    children~ : Array[ReaderItem],
    /// Semantically owned by this physical paragraph, erased as SIBLINGS
    /// after it -- the descendants-become-siblings behaviour the extras
    /// passes implement, kept structural here so no conversion can quietly
    /// flatten it into ordinary children.
    extras~ : Array[ReaderItem],
    properties~ : ParagraphProperties
  )
  ReaderRun(children~ : Array[ReaderItem], properties~ : RunProperties)
  ReaderText(String)
  ReaderTab
  ReaderCheckbox(Bool)
  ReaderHyperlink(
    children~ : Array[ReaderItem],
    href~ : String?,
    anchor~ : String?,
    target_frame~ : String?
  )
  ReaderNoteReference(note_type~ : String, note_id~ : String)
  ReaderCommentReference(String)
  ReaderImage(Image)
  ReaderTable(children~ : Array[ReaderItem], properties~ : TableProperties)
  ReaderTableRow(children~ : Array[ReaderItem], is_header~ : Bool)
  ReaderTableCell(
    children~ : Array[ReaderItem],
    col_span~ : Int,
    row_span~ : Int
  )
  ReaderBreak(@document.BreakType)
  ReaderBookmarkStart(String)
}

///|
/// The erase boundary: collapse carrier items into the public tree shape.
/// An accumulator, because a paragraph's extras expand into the CONTAINING
/// array after the paragraph itself.
fn reader_items_to_elements(
  items : Array[ReaderItem],
) -> Array[DocumentElement] {
  let out : Array[DocumentElement] = []
  erase_reader_items_into(items, out)
  out
}

///|
fn erase_reader_items_into(
  items : Array[ReaderItem],
  out : Array[DocumentElement],
) -> Unit {
  for item in items {
    match item {
      Trace(_) => ()
      New(tree) =>
        match tree.shape {
          ReaderParagraph(inputs=_, children~, extras~, properties~) => {
            out.push(
              Paragraph(
                children=reader_items_to_elements(children),
                properties~,
              ),
            )
            erase_reader_items_into(extras, out)
          }
          ReaderRun(children~, properties~) =>
            out.push(
              Run(children=reader_items_to_elements(children), properties~),
            )
          ReaderText(value) => out.push(Text(value))
          ReaderTab => out.push(Tab)
          ReaderCheckbox(checked) => out.push(Checkbox(checked))
          ReaderHyperlink(children~, href~, anchor~, target_frame~) =>
            out.push(
              Hyperlink(
                children=reader_items_to_elements(children),
                href~,
                anchor~,
                target_frame~,
              ),
            )
          ReaderNoteReference(note_type~, note_id~) =>
            out.push(NoteReference(note_type~, note_id~))
          ReaderCommentReference(id) => out.push(CommentReference(id))
          ReaderImage(image) => out.push(Image(image))
          ReaderTable(children~, properties~) =>
            out.push(
              Table(children=reader_items_to_elements(children), properties~),
            )
          ReaderTableRow(children~, is_header~) =>
            out.push(
              TableRow(children=reader_items_to_elements(children), is_header~),
            )
          ReaderTableCell(children~, col_span~, row_span~) =>
            out.push(
              TableCell(
                children=reader_items_to_elements(children),
                col_span~,
                row_span~,
              ),
            )
          ReaderBreak(kind) => out.push(Break(kind))
          ReaderBookmarkStart(name) => out.push(BookmarkStart(name))
        }
    }
  }
}

///|
/// The effect stream in document order: a tree's own effects come before its
/// children's (the decision happens at the node), and a paragraph's extras
/// follow its children, matching the erase order.
#warnings("-unused_value")
fn collect_reader_effects(items : Array[ReaderItem]) -> Array[ReaderEffect] {
  let out : Array[ReaderEffect] = []
  collect_reader_effects_into(items, out)
  out
}

///|
fn collect_reader_effects_into(
  items : Array[ReaderItem],
  out : Array[ReaderEffect],
) -> Unit {
  for item in items {
    match item {
      Trace(effect) => out.push(effect)
      New(tree) => {
        out.append(tree.local_effects)
        match tree.shape {
          ReaderParagraph(inputs=_, children~, extras~, properties=_) => {
            collect_reader_effects_into(children, out)
            collect_reader_effects_into(extras, out)
          }
          ReaderRun(children~, properties=_) =>
            collect_reader_effects_into(children, out)
          ReaderHyperlink(children~, ..) =>
            collect_reader_effects_into(children, out)
          ReaderTable(children~, properties=_) =>
            collect_reader_effects_into(children, out)
          ReaderTableRow(children~, is_header=_) =>
            collect_reader_effects_into(children, out)
          ReaderTableCell(children~, ..) =>
            collect_reader_effects_into(children, out)
          ReaderText(_)
          | ReaderTab
          | ReaderCheckbox(_)
          | ReaderNoteReference(..)
          | ReaderCommentReference(_)
          | ReaderImage(_)
          | ReaderBreak(_)
          | ReaderBookmarkStart(_) => ()
        }
      }
    }
  }
}

///|
/// The provenance sidecar for the erase boundary: one record per tree
/// Paragraph occurrence, in exactly the order the erased tree presents
/// them to a depth-first walk — paragraph first, its children (nested
/// paragraphs included) next, its extras as following siblings. Each
/// record is the ordered physical `w:p` identity vector of the
/// occurrence's inputs; a missing identity stays None and makes the
/// occurrence unjoinable rather than inventing one.
fn collect_paragraph_provenance(
  items : Array[ReaderItem],
) -> Array[Array[SourceElementId?]] {
  let out : Array[Array[SourceElementId?]] = []
  collect_paragraph_provenance_into(items, out)
  out
}

///|
fn collect_paragraph_provenance_into(
  items : Array[ReaderItem],
  out : Array[Array[SourceElementId?]],
) -> Unit {
  for item in items {
    match item {
      Trace(_) => ()
      New(tree) =>
        match tree.shape {
          ReaderParagraph(inputs~, children~, extras~, properties=_) => {
            let vector : Array[SourceElementId?] = []
            for input in inputs {
              vector.push(input.paragraph.source_element_id)
            }
            out.push(vector)
            collect_paragraph_provenance_into(children, out)
            collect_paragraph_provenance_into(extras, out)
          }
          ReaderRun(children~, properties=_) =>
            collect_paragraph_provenance_into(children, out)
          ReaderHyperlink(children~, ..) =>
            collect_paragraph_provenance_into(children, out)
          ReaderTable(children~, properties=_) =>
            collect_paragraph_provenance_into(children, out)
          ReaderTableRow(children~, is_header=_) =>
            collect_paragraph_provenance_into(children, out)
          ReaderTableCell(children~, ..) =>
            collect_paragraph_provenance_into(children, out)
          ReaderText(_)
          | ReaderTab
          | ReaderCheckbox(_)
          | ReaderNoteReference(..)
          | ReaderCommentReference(_)
          | ReaderImage(_)
          | ReaderBreak(_)
          | ReaderBookmarkStart(_) => ()
        }
    }
  }
}