///|
/// StreamBuffer - cursor-safe partial rendering with a dedicated App buffer

///|
pub(all) struct StreamBuffer {
  app : Ref[@render.App]
  width : Ref[Int]
  height : Ref[Int]
}

///|
pub fn StreamBuffer::new(width : Int, height : Int) -> StreamBuffer {
  {
    app: { val: @render.App::new(width, height) },
    width: { val: width },
    height: { val: height },
  }
}

///|
pub fn StreamBuffer::resize(
  self : StreamBuffer,
  width : Int,
  height : Int,
) -> Unit {
  self.width.val = width
  self.height.val = height
  self.app.val = @render.App::new(width, height)
}

///|
pub fn StreamBuffer::size(self : StreamBuffer) -> (Int, Int) {
  (self.width.val, self.height.val)
}

///|
pub fn StreamBuffer::clear(self : StreamBuffer) -> Unit {
  self.app.val.clear_prev_buffer()
}

///|
pub fn StreamBuffer::sync(
  self : StreamBuffer,
  component : @core.Component,
) -> Unit {
  self.app.val.clear_prev_buffer()
  ignore(self.app.val.render(component))
}

///|
/// Render component at 0-indexed row/col while preserving cursor position
pub fn StreamBuffer::render(
  self : StreamBuffer,
  component : @core.Component,
  row? : Int = 0,
  col? : Int = 0,
  preserve_cursor? : Bool = true,
) -> Unit {
  let output = self.render_output(component, row~, col~, preserve_cursor~)
  @io.print_raw(output)
}

///|
pub fn StreamBuffer::render_output(
  self : StreamBuffer,
  component : @core.Component,
  row? : Int = 0,
  col? : Int = 0,
  preserve_cursor? : Bool = true,
) -> String {
  let mut output = ""
  if preserve_cursor {
    output = output + ansi_save_cursor()
  }
  // render_with_offset handles both full render (when prev_buffer is None)
  // and diff render (when prev_buffer exists)
  output = output +
    self.app.val.render_with_offset(component, row_offset=row, col_offset=col)
  if preserve_cursor {
    output = output + ansi_restore_cursor()
  }
  output
}