///|
/// A read-only markdown viewer that renders markdown source using the same
/// `MarkdownDocumentSession` pipeline as the markdown editor, but without
/// editing capabilities (no caret, selection, IME, or keyboard input).
///
/// The viewer is designed for embedding inside a `scroll_view` or similar
/// scrollable parent. Pass `visible_scroll_y`, `visible_viewport_height`, and
/// `visible_overscan` so the paint pass can cull off-screen blocks for
/// performance, matching the session editor's windowed rendering strategy.
pub fn[Msg] markdown_viewer(
  source~ : String,
  size~ : @core.Size,
  font? : @core.FontSpec = @core.FontSpec::new(),
  foreground? : @core.Color = @core.Color::black(),
  background? : @core.Brush = @core.Brush::solid(
    @core.Color::rgba(r=0.0, g=0.0, b=0.0, a=0.0),
  ),
  corner_radius? : Double = 0.0,
  on_link_click? : ((String) -> Msg)? = None,
  visible_scroll_y? : Double = 0.0,
  visible_viewport_height? : Double = 0.0,
  visible_overscan? : Double = 320.0,
  visible_scroll_state? : @core.ScrollState? = None,
  key? : String? = None,
) -> @core.View[Msg] {
  let cache = RichTextDocumentViewCache::new()
  @core.View::node(
    identity=() => @core.ViewIdentity::new(kind="MarkdownViewer", key~),
    revision=() => {
      "markdown-viewer:\{source.length()}:\{view_size_revision(size)}:\{view_font_revision(font)}:\{view_color_revision(foreground)}:\{view_brush_revision(background)}:\{corner_radius}"
    },
    paint_revision=() => {
      "markdown-viewer-scroll:\{markdown_session_current_scroll_y(visible_scroll_y, visible_scroll_state)}"
    },
    layout=ctx => {
      let measured_height = rich_text_document_session_view_measure_height(
        source,
        size,
        font,
        ctx.text_system,
        cache,
      )
      let measured = @core.Size::new(width=size.width, height=measured_height)
      @core.ViewLayoutResult::new(size=ctx.constraints.constrain(measured))
    },
    paint=ctx => {
      rich_text_document_session_view_paint(
        ctx, source, font, foreground, background, corner_radius, visible_scroll_y,
        visible_viewport_height, visible_overscan, visible_scroll_state, cache,
      )
    },
    event=ctx => {
      markdown_viewer_event(
        ctx,
        source~,
        font~,
        on_link_click~,
        visible_scroll_y~,
        visible_viewport_height~,
        visible_overscan~,
        visible_scroll_state~,
      )
    },
    semantics=() => {
      @core.ViewSemanticsInfo::new(
        role=@core.SemanticsRole::ScrollView,
        label="Markdown document",
      )
    },
  )
}

///|
fn[Msg] markdown_viewer_event(
  ctx : @core.ViewEventContext,
  source~ : String,
  font~ : @core.FontSpec,
  on_link_click~ : ((String) -> Msg)?,
  visible_scroll_y~ : Double,
  visible_viewport_height~ : Double,
  visible_overscan~ : Double,
  visible_scroll_state~ : @core.ScrollState?,
) -> @core.ViewEventResult[Msg] {
  match on_link_click {
    Some(handler) =>
      match ctx.event {
        @core.AppEvent::Pointer(event) =>
          if event.phase == @core.PointerPhase::Up && event.button == 0 {
            match
              rich_text_document_session_view_offset_at_point(
                source,
                event.position,
                ctx.frame,
                font,
                ctx.text_system,
                visible_scroll_y,
                visible_viewport_height,
                visible_overscan,
                visible_scroll_state,
              ) {
              Some(offset) =>
                match markdown_editor_link_context_at_caret(source, offset) {
                  Some(context) =>
                    match
                      markdown_editor_reference_target(source, context.bounds) {
                      Some(target) =>
                        view_event_result(captured=true, messages=[
                          handler(target),
                        ])
                      None => view_event_result()
                    }
                  None => view_event_result()
                }
              None => view_event_result()
            }
          } else {
            view_event_result()
          }
        _ => view_event_result()
      }
    None => view_event_result()
  }
}