///| Inline rendering for the literal (source-preserving) renderer.

///|

///| Split from renderer_literal.mbt to keep that file focused on the

///| document / block-level dispatch. Helpers (`LiteralOpts`,

///| `write_marker`, `write_image_preview_slot`, `repeat_char`,

///| `escape_html` …) live alongside in the same package.

///|
fn render_inlines_literal(
  inlines : Array[Inline],
  buf : StringBuilder,
  opts : LiteralOpts,
) -> Unit {
  for inline in inlines {
    render_inline_literal(inline, buf, opts)
  }
}

///|
/// Paragraph renderer variant that can attach a block preview after an
/// image URL that occupies a whole source line.
fn render_paragraph_inlines_literal(
  inlines : Array[Inline],
  buf : StringBuilder,
  opts : LiteralOpts,
) -> Unit {
  let len = inlines.length()
  let mut at_line_start = true
  for i = 0; i < len; i = i + 1 {
    let inline = inlines[i]
    match inline {
      Inline::SoftBreak(..) | Inline::HardBreak(..) => {
        render_inline_literal(inline, buf, opts)
        at_line_start = true
      }
      Inline::Image(alt~, url~, title~, ..) =>
        if opts.image_preview &&
          at_line_start &&
          is_line_end_after(inlines, i) &&
          is_previewable_image_url(url) {
          render_inline_literal(inline, buf, opts.without_image_preview())
          write_image_preview_block_slot(buf, url, alt, title)
          at_line_start = false
        } else {
          render_inline_literal(inline, buf, opts)
          at_line_start = false
        }
      _ => {
        render_inline_literal(inline, buf, opts)
        at_line_start = false
      }
    }
  }
}

///|
fn is_line_end_after(inlines : Array[Inline], index : Int) -> Bool {
  if index + 1 >= inlines.length() {
    return true
  }
  match inlines[index + 1] {
    Inline::SoftBreak(..) | Inline::HardBreak(..) => true
    _ => false
  }
}

///|
fn render_inline_literal(
  inline : Inline,
  buf : StringBuilder,
  opts : LiteralOpts,
) -> Unit {
  // Inline spans come from the inline parser and are relative to the
  // surrounding block's content, not the document. They are intentionally
  // omitted from `data-src-*` annotations — the block-level ancestor's
  // span is enough to recover precise source offsets via the visible-text
  // invariant.
  match inline {
    Inline::Text(content~, ..) => escape_html_into(buf, content)
    Inline::SoftBreak(..) => buf.write_char('\n')
    Inline::HardBreak(..) => {
      write_marker(buf, "\\")
      buf.write_char('\n')
    }
    Inline::Emphasis(children~, ..) => {
      buf.write_string("")
      write_marker(buf, "*")
      render_inlines_literal(children, buf, opts)
      write_marker(buf, "*")
      buf.write_string("")
    }
    Inline::Strong(children~, ..) => {
      buf.write_string("")
      write_marker(buf, "**")
      render_inlines_literal(children, buf, opts)
      write_marker(buf, "**")
      buf.write_string("")
    }
    Inline::Strikethrough(children~, ..) => {
      buf.write_string("")
      write_marker(buf, "~~")
      render_inlines_literal(children, buf, opts)
      write_marker(buf, "~~")
      buf.write_string("")
    }
    Inline::Code(content~, ..) => {
      let backticks = calc_code_span_backticks(content)
      let needs_padding = content.length() > 0 &&
        ({
          let first = content.get_char(0)
          let last = content.get_char(content.length() - 1)
          first == Some('`') || last == Some('`')
        })
      buf.write_string("")
      write_marker_repeat(buf, '`', backticks)
      if needs_padding {
        buf.write_char(' ')
      }
      escape_html_into(buf, content)
      if needs_padding {
        buf.write_char(' ')
      }
      write_marker_repeat(buf, '`', backticks)
      buf.write_string("")
    }
    Inline::WikiLink(target~, label~, fragment~, ..) => {
      let href = if fragment.is_empty() {
        target
      } else {
        target + "#" + fragment
      }
      buf.write_string("")
      write_marker(buf, "[[")
      escape_html_into(buf, target)
      if !fragment.is_empty() {
        escape_html_into(buf, "#" + fragment)
      }
      if !label.is_empty() {
        write_marker(buf, "|")
        escape_html_into(buf, label)
      }
      write_marker(buf, "]]")
      buf.write_string("")
    }
    Inline::Link(children~, url~, title~, ..) => {
      buf.write_string("')
      write_marker(buf, "[")
      render_inlines_literal(children, buf, opts)
      let trailing = if title.is_empty() {
        "](" + url + ")"
      } else {
        "](" + url + " \"" + title + "\")"
      }
      write_marker(buf, trailing)
      buf.write_string("")
    }
    Inline::RefLink(children~, label~, ..) => {
      buf.write_string("")
      write_marker(buf, "[")
      render_inlines_literal(children, buf, opts)
      write_marker(buf, "][" + label + "]")
      buf.write_string("")
    }
    Inline::Autolink(url~, is_email~, ..) => {
      let href = if is_email { "mailto:" + url } else { url }
      buf.write_string("")
      write_marker(buf, "<")
      escape_html_into(buf, url)
      write_marker(buf, ">")
      buf.write_string("")
    }
    Inline::Image(alt~, url~, title~, ..) => {
      let meta = parse_image_preview_alt(alt)
      buf.write_string("")
      write_marker(buf, "![")
      escape_html_into(buf, alt)
      let trailing = if title.is_empty() {
        "](" + url + ")"
      } else {
        "](" + url + " \"" + title + "\")"
      }
      write_marker(buf, trailing)
      if opts.image_preview {
        write_image_preview_slot(buf, url, alt, title)
      }
      buf.write_string("")
    }
    Inline::RefImage(alt~, label~, ..) => {
      // Reference images don't carry an URL inline; the slot stays empty.
      // The consumer can resolve `data-md-image-ref` against their own
      // definition map and assign a `src` later.
      let meta = parse_image_preview_alt(alt)
      buf.write_string("")
      write_marker(buf, "![")
      escape_html_into(buf, alt)
      write_marker(buf, "][" + label + "]")
      if opts.image_preview {
        write_image_preview_slot_open(buf, meta)
        buf.write_string("\"")")
        buf.write_string("")
      }
      buf.write_string("")
    }
    Inline::HtmlInline(html~, ..) => {
      buf.write_string("")
      escape_html_into(buf, html)
      buf.write_string("")
    }
    Inline::FootnoteReference(label~, ..) => {
      // The `[^label]` source characters are the only content, but they
      // sit inside an `aria-hidden` marker span — without an explicit
      // `aria-label` the `` would be focusable yet nameless.
      buf.write_string("")
      write_marker(buf, "[^" + label + "]")
      buf.write_string("")
    }
  }
}