///|
/// TUI Application renderer with reactive signals integration
/// Provides ink-like API for building terminal UIs

///|
/// App manages the render loop and diff-based updates
pub struct App {
  width : Int
  height : Int
  mut prev_buffer : CharBuffer?
  mut last_layout : @ccore.Layout?
  handlers : @events.EventHandlers
}

///|
pub fn App::new(width : Int, height : Int) -> App {
  {
    width,
    height,
    prev_buffer: None,
    last_layout: None,
    handlers: @events.EventHandlers::new(),
  }
}

///|
/// Render a component tree to ANSI string
pub fn App::render(self : App, component : @core.Component) -> String {
  // Compute layout
  let layout = compute_layout(component.get_node(), self.width, self.height)

  // Save layout for hit testing
  self.last_layout = Some(layout)

  // Create new buffer
  let buf = CharBuffer::new(self.width, self.height)
  render_layout(
    buf,
    layout,
    component.get_styles(),
    component.get_texts(),
    0,
    0,
  )

  // Generate output with diff if possible
  let output = match self.prev_buffer {
    Some(prev) => buf.diff_to_ansi(prev)
    None =>
      // First render: clear screen and render full
      // to_ansi already handles cursor positioning, so no need for explicit move
      ansi_clear_screen() + buf.to_ansi()
  }

  // Save current buffer for next diff
  self.prev_buffer = Some(buf)
  output
}

///|
/// Render a component tree with a cursor offset (no screen clear)
pub fn App::render_with_offset(
  self : App,
  component : @core.Component,
  row_offset? : Int = 0,
  col_offset? : Int = 0,
) -> String {
  let layout = compute_layout(component.get_node(), self.width, self.height)
  self.last_layout = Some(layout)
  let buf = CharBuffer::new(self.width, self.height)
  render_layout(
    buf,
    layout,
    component.get_styles(),
    component.get_texts(),
    0,
    0,
  )
  let output = match self.prev_buffer {
    Some(prev) => buf.diff_to_ansi_offset(prev, row_offset, col_offset)
    None => buf.to_ansi_offset(row_offset, col_offset)
  }
  self.prev_buffer = Some(buf)
  output
}

///|
/// Render with cursor hidden and positioned at origin
pub fn App::render_frame(self : App, component : @core.Component) -> String {
  ansi_hide_cursor() + self.render(component)
}

///|
/// Create a reactive effect that re-renders when signals change
/// Returns a dispose function
pub fn App::create_render_effect(
  self : App,
  render_fn : () -> @core.Component,
  output_fn : (String) -> Unit,
) -> () -> Unit {
  @signals.render_effect(fn() {
    let component = render_fn()
    let output = self.render_frame(component)
    output_fn(output)
  })
}

///|
/// Initialize terminal (enter alt screen, hide cursor)
pub fn App::init_terminal() -> String {
  ansi_enter_alt_screen() + ansi_hide_cursor() + ansi_clear_screen()
}

///|
/// Restore terminal (show cursor, leave alt screen)
pub fn App::restore_terminal() -> String {
  ansi_show_cursor() + ansi_leave_alt_screen()
}

///|
/// Full-screen app helper: sets up alt screen and handles cleanup
pub fn run_app(
  width : Int,
  height : Int,
  render_fn : () -> @core.Component,
  output_fn : (String) -> Unit,
) -> () -> Unit {
  let app = App::new(width, height)

  // Initialize terminal
  output_fn(App::init_terminal())

  // Create render effect
  let dispose = app.create_render_effect(render_fn, output_fn)

  // Return cleanup function
  fn() {
    dispose()
    output_fn(App::restore_terminal())
  }
}

///|
/// Simple render function for one-shot rendering (no reactivity)
pub fn render_once(
  width : Int,
  height : Int,
  component : @core.Component,
) -> String {
  let app = App::new(width, height)
  app.render(component)
}

///|
/// Register a click handler for a component ID
pub fn App::on_click(self : App, id : String, handler : () -> Unit) -> Unit {
  self.handlers.on_click(id, @events.ClickHandler::new(handler))
}

///|
/// Handle a mouse event
/// Returns true if an event handler was invoked
pub fn App::handle_mouse(self : App, event : @events.MouseEvent) -> Bool {
  match event.event_type {
    @events.MouseEventType::Press =>
      match event.button {
        @events.MouseButton::Left =>
          match self.last_layout {
            Some(layout) =>
              @events.handle_click(layout, self.handlers, event.x, event.y)
            None => false
          }
        _ => false
      }
    _ => false
  }
}

///|
/// Perform hit test at coordinates
pub fn App::hit_test(self : App, x : Int, y : Int) -> @events.HitTestResult? {
  match self.last_layout {
    Some(layout) => @events.hit_test_root(layout, x, y)
    None => None
  }
}

///|
/// Find component by ID
pub fn App::find_by_id(self : App, id : String) -> @events.HitTestResult? {
  match self.last_layout {
    Some(layout) => @events.find_layout_by_id_root(layout, id)
    None => None
  }
}

///|
/// Clear the previous buffer to force a full re-render on next render call
pub fn App::clear_prev_buffer(self : App) -> Unit {
  self.prev_buffer = None
}

///|
/// Dispatch a click event to handlers based on hit test result
/// Returns the clicked component ID if found, None otherwise
pub fn App::dispatch_click(
  self : App,
  event : @events.MouseEvent,
  handlers : Map[String, () -> Unit],
) -> String? {
  // Only handle left click press
  match (event.event_type, event.button) {
    (@events.MouseEventType::Press, @events.MouseButton::Left) =>
      match self.hit_test(event.x, event.y) {
        Some(hit) => {
          match handlers.get(hit.id) {
            Some(handler) => handler()
            None => ()
          }
          Some(hit.id)
        }
        None => None
      }
    _ => None
  }
}

///|
/// Dispatch a click event, returns hit result for custom handling
pub fn App::dispatch_click_with_result(
  self : App,
  event : @events.MouseEvent,
) -> @events.HitTestResult? {
  match (event.event_type, event.button) {
    (@events.MouseEventType::Press, @events.MouseButton::Left) =>
      self.hit_test(event.x, event.y)
    _ => None
  }
}

///|
/// Dispatch hover event - returns hovered component ID on Move/Drag events
pub fn App::dispatch_hover(self : App, event : @events.MouseEvent) -> String? {
  match (event.event_type, event.button) {
    (@events.MouseEventType::Drag, @events.MouseButton::None)
    | (@events.MouseEventType::Move, _) =>
      match self.hit_test(event.x, event.y) {
        Some(hit) => Some(hit.id)
        None => Some("")
      }
    _ => None
  }
}

///|
/// Dispatch a click event using Component's click_handlers
/// Returns the clicked component ID if found and handler was invoked, None otherwise
pub fn App::dispatch_click_from_component(
  self : App,
  event : @events.MouseEvent,
  component : @core.Component,
) -> String? {
  // Only handle left click press
  match (event.event_type, event.button) {
    (@events.MouseEventType::Press, @events.MouseButton::Left) =>
      match self.hit_test(event.x, event.y) {
        Some(hit) => {
          match component.get_click_handlers().get(hit.id) {
            Some(handler) => handler.invoke()
            None => ()
          }
          Some(hit.id)
        }
        None => None
      }
    _ => None
  }
}