///|
/// StreamingText - Component for streaming text display with typing animation

///|
/// Streaming state for managing streaming text display
pub(all) struct StreamingState {
  /// Full text to be streamed
  full_text : @signals.Signal[String]
  /// Current display position (0 to full_text.length)
  position : @signals.Signal[Int]
  /// Ticker ID for animation (0 = not running)
  ticker_id : Ref[Int]
}

///|
/// Create a new streaming state
pub fn StreamingState::new() -> StreamingState {
  {
    full_text: @signals.signal(""),
    position: @signals.signal(0),
    ticker_id: { val: 0 },
  }
}

///|
/// Check if streaming is in progress
pub fn StreamingState::is_streaming(self : StreamingState) -> Bool {
  let full = self.full_text.get()
  let pos = self.position.get()
  full.length() > 0 && pos < full.length()
}

///|
/// Check if streaming has content (either in progress or completed but not cleared)
pub fn StreamingState::has_content(self : StreamingState) -> Bool {
  self.position.get() > 0
}

///|
/// Get the partial text (up to current position)
pub fn StreamingState::get_partial(self : StreamingState) -> String {
  let full = self.full_text.get()
  let pos = self.position.get()
  get_substring(full, 0, pos)
}

///|
/// Get the partial text with cursor
pub fn StreamingState::get_partial_with_cursor(
  self : StreamingState,
  cursor? : String = "▌",
) -> String {
  self.get_partial() + cursor
}

///|
/// Get progress as (current, total)
pub fn StreamingState::get_progress(self : StreamingState) -> (Int, Int) {
  (self.position.get(), self.full_text.get().length())
}

///|
/// Get progress as percentage (0.0 to 1.0)
pub fn StreamingState::get_progress_ratio(self : StreamingState) -> Double {
  let total = self.full_text.get().length()
  if total == 0 {
    0.0
  } else {
    self.position.get().to_double() / total.to_double()
  }
}

///|
/// Start streaming text with given interval
/// Returns true if streaming started, false if already streaming
/// on_tick is called after each character advance
/// on_complete is called when streaming finishes
pub fn StreamingState::start(
  self : StreamingState,
  text : String,
  interval_ms : Int,
  on_tick : () -> Unit,
  on_complete : () -> Unit,
) -> Bool {
  // Don't start if already streaming
  if self.ticker_id.val != 0 {
    return false
  }
  self.full_text.set(text)
  self.position.set(0)
  // Create tick function that advances position
  fn tick() {
    let full = self.full_text.get()
    let pos = self.position.get()
    if pos < full.length() {
      self.position.set(pos + 1)
      on_tick()
    } else {
      // Done streaming
      self.stop()
      on_complete()
    }
  }

  let ticker_id = @io.start_render_ticker(interval_ms, tick)
  self.ticker_id.val = ticker_id
  // If ticker_id is 0, native mode doesn't support tickers
  // Complete immediately in this case
  if ticker_id == 0 {
    self.position.set(text.length())
    on_complete()
  }
  true
}

///|
/// Stop streaming and clear the ticker
pub fn StreamingState::stop(self : StreamingState) -> Unit {
  if self.ticker_id.val != 0 {
    @io.stop_render_ticker(self.ticker_id.val)
    self.ticker_id.val = 0
  }
}

///|
/// Clear streaming state (stop and reset)
pub fn StreamingState::clear(self : StreamingState) -> Unit {
  self.stop()
  self.full_text.set("")
  self.position.set(0)
}

///|
/// Get the full text and clear the state
/// Useful for adding completed text to a message list
pub fn StreamingState::take(self : StreamingState) -> String {
  let text = self.full_text.get()
  self.clear()
  text
}

///|
/// Get the partial text (up to current position) and clear the state
/// Useful for interrupting streaming and keeping what was shown
pub fn StreamingState::take_partial(self : StreamingState) -> String {
  let text = self.get_partial()
  self.clear()
  text
}

///|
/// Complete streaming immediately (show all text)
pub fn StreamingState::complete(self : StreamingState) -> Unit {
  self.stop()
  let full = self.full_text.get()
  self.position.set(full.length())
}

///|
/// Append text chunk for real-time streaming (e.g., from API)
/// Unlike start(), this immediately shows appended text
pub fn StreamingState::append(self : StreamingState, chunk : String) -> Unit {
  let current = self.full_text.get()
  let new_text = current + chunk
  self.full_text.set(new_text)
  // Show all text immediately (no animation delay)
  self.position.set(new_text.length())
}

///|
/// Start real-time streaming mode (for API streaming)
/// Call append() to add chunks, then call complete() when done
pub fn StreamingState::start_realtime(self : StreamingState) -> Bool {
  // Don't start if already has content
  if self.has_content() {
    return false
  }
  self.full_text.set("")
  self.position.set(0)
  true
}

// Helper function to get substring by iterating characters

///|
fn get_substring(s : String, start : Int, end : Int) -> String {
  let sb = StringBuilder::new()
  let mut idx = 0
  for c in s {
    if idx >= start && idx < end {
      sb.write_char(c)
    }
    idx = idx + 1
  }
  sb.to_string()
}

///|
/// Split string by newlines (useful for multi-line streaming content)
pub fn split_lines(text : String) -> Array[String] {
  let result : Array[String] = []
  let mut current = StringBuilder::new()
  for c in text {
    if c == '\n' {
      result.push(current.to_string())
      current = StringBuilder::new()
    } else {
      current.write_char(c)
    }
  }
  result.push(current.to_string())
  result
}

///|
/// Count lines in text
pub fn count_lines(text : String) -> Int {
  if text.length() == 0 {
    return 1
  }
  let mut lines = 1
  for c in text {
    if c == '\n' {
      lines = lines + 1
    }
  }
  lines
}