///|
/// Program configuration and entry-point for a Pippa TUI application.
///
/// A `Program[Model, Msg]` ties together the three core functions of the Elm
/// Architecture (init, update, view) and drives the main event loop.
///
/// Construct with `Program::new` and run with `Program::run`.
///
/// The common `view~ : Model -> String` shape is still the default. Apps that
/// need per-render terminal state can call `Program::with_structured_view`; when
/// present, the runtime prefers it and renders its `content`.

///|
/// Configuration for a Pippa application.
pub(all) struct Program[Model, Msg] {
  /// Initial state and optional initial command.
  init : () -> UpdateResult[Model, Msg]
  /// Pure update function: given the current model and a message, produce
  /// the next model and an optional command.
  update : (Model, Msg) -> UpdateResult[Model, Msg]
  /// Pure view function: render the current model to a string.
  view : (Model) -> String
  /// Optional structured view function carrying content plus per-frame terminal
  /// state. When set, it is preferred over `view`.
  structured_view : ((Model) -> View)?
  /// Map input events to messages. Return None to ignore an event.
  sub : (InputEvent) -> Msg?
  /// Produce a message when a tick command fires. Prefer `Cmd::after` for
  /// component-local clocks; `on_tick` is a compatibility hook for app-wide
  /// timers.
  on_tick : () -> Msg?
  /// Enable terminal mouse reporting while the program runs.
  mouse : Bool
  /// Enter the terminal alternate screen buffer while the program runs.
  alt_screen : Bool
  /// Enable terminal focus reporting while the program runs.
  focus_reporting : Bool
  /// Enable bracketed paste while the program runs.
  bracketed_paste : Bool
  /// Hide the terminal cursor while the program runs.
  hide_cursor : Bool
  /// Wrap each rendered frame in synchronized output mode.
  synchronized_output : Bool
  /// Render view output and screen control sequences.
  rendering : Bool
  /// Read input events from stdin and enable terminal input modes.
  read_stdin : Bool
  /// Optional non-blocking input source polled by the runtime before waiting
  /// on stdin/timers. Return currently available events and never block.
  input : (() -> Array[InputEvent])?
  /// Optional non-blocking raw byte source polled before waiting on stdin/timers.
  /// Return currently available bytes and never block.
  raw_input : (() -> Bytes)?
  /// Optional cancellation predicate polled by the runtime between waits.
  cancelled : (() -> Bool)?
  /// Maximum renderer FPS. Values outside 1..120 are clamped by the runtime.
  fps : Int
  /// Optional initial window width used to seed `on_resize`.
  window_width : Int
  /// Optional initial window height used to seed `on_resize`.
  window_height : Int
  /// Write rendered output and terminal control sequences.
  output : (String) -> Unit
  /// Produce a message when the terminal size changes using a typed payload.
  on_window_size : (WindowSize) -> Msg?
  /// Produce a message when the terminal size changes. Return None to ignore it.
  on_resize : (Int, Int) -> Msg?
  /// Return true to exit the event loop after an update.
  should_quit : (Model) -> Bool
  /// Intercept Ctrl+C and force-quit before delivering the event to `sub`.
  /// Set to false if the application wants to handle Ctrl+C itself (e.g. copy
  /// mode, graceful cancel). Defaults to true.
  catch_interrupt : Bool
  /// Produce a message for runtime lifecycle events.
  on_lifecycle : (LifecycleMsg) -> Msg?
  /// Run an external process while Pippa has released the terminal.
  exec_runner : (ExecProcess) -> ExecResult
  /// Stop the process for terminal suspension and return after continue.
  suspend_runner : () -> Unit
}

///|
/// Terminal control requests that can be sent through `ProgramHandle`.
#warnings("-unused_constructor")
priv enum RuntimeControl {
  ReleaseTerminal
  RestoreTerminal
}

///|
/// Handle for sending messages to a running program.
///
/// `send` is intended for the same MoonBit runtime thread. Native cross-thread
/// delivery of generic MoonBit values is not supported by the runtime today.
struct ProgramHandle[Msg] {
  pending_msgs : @queue.Queue[Msg]
  pending_controls : @queue.Queue[RuntimeControl]
  wake : () -> Unit
  closed : Ref[Bool]
}

///|
fn[Msg] ProgramHandle::new(
  pending_msgs : @queue.Queue[Msg],
  pending_controls : @queue.Queue[RuntimeControl],
  wake : () -> Unit,
  closed : Ref[Bool],
) -> ProgramHandle[Msg] {
  { pending_msgs, pending_controls, wake, closed }
}

///|
/// Enqueue a message for delivery to the running program.
pub fn[Msg] ProgramHandle::send(self : ProgramHandle[Msg], msg : Msg) -> Bool {
  if self.closed.val {
    false
  } else {
    self.pending_msgs.push(msg)
    (self.wake)()
    true
  }
}

///|
/// Returns true after the program has exited and the handle can no longer send.
pub fn[Msg] ProgramHandle::is_closed(self : ProgramHandle[Msg]) -> Bool {
  self.closed.val
}

///|
/// Temporarily restore the terminal to its outer shell state.
pub fn[Msg] ProgramHandle::release_terminal(self : ProgramHandle[Msg]) -> Bool {
  if self.closed.val {
    false
  } else {
    self.pending_controls.push(ReleaseTerminal)
    (self.wake)()
    true
  }
}

///|
/// Re-enter Pippa's managed terminal state after `release_terminal`.
pub fn[Msg] ProgramHandle::restore_terminal(self : ProgramHandle[Msg]) -> Bool {
  if self.closed.val {
    false
  } else {
    self.pending_controls.push(RestoreTerminal)
    (self.wake)()
    true
  }
}

///|
/// Create a new Program from init, update, and view functions.
pub fn[Model, Msg] Program::new(
  init~ : () -> UpdateResult[Model, Msg],
  update~ : (Model, Msg) -> UpdateResult[Model, Msg],
  view~ : (Model) -> String,
  sub? : (InputEvent) -> Msg? = fn(_evt) { None },
  on_tick? : () -> Msg? = fn() { None },
  mouse? : Bool = false,
  alt_screen? : Bool = true,
  focus_reporting? : Bool = true,
  bracketed_paste? : Bool = true,
  hide_cursor? : Bool = true,
  synchronized_output? : Bool = true,
  rendering? : Bool = true,
  read_stdin? : Bool = true,
  input? : (() -> Array[InputEvent])? = None,
  raw_input? : (() -> Bytes)? = None,
  cancelled? : (() -> Bool)? = None,
  fps? : Int = 60,
  window_width? : Int = 0,
  window_height? : Int = 0,
  output? : (String) -> Unit = default_output,
  on_resize? : (Int, Int) -> Msg? = fn(_cols, _rows) { None },
  should_quit? : (Model) -> Bool = fn(_model) { false },
  catch_interrupt? : Bool = true,
  on_window_size? : (WindowSize) -> Msg? = fn(_size) { None },
  on_lifecycle? : (LifecycleMsg) -> Msg? = fn(_msg) { None },
  exec_runner? : (ExecProcess) -> ExecResult = default_exec_runner,
  suspend_runner? : () -> Unit = default_suspend_runner,
) -> Program[Model, Msg] {
  {
    init,
    update,
    view,
    structured_view: None,
    sub,
    on_tick,
    mouse,
    alt_screen,
    focus_reporting,
    bracketed_paste,
    hide_cursor,
    synchronized_output,
    rendering,
    read_stdin,
    input,
    raw_input,
    cancelled,
    fps,
    window_width,
    window_height,
    output,
    on_window_size,
    on_resize,
    should_quit,
    catch_interrupt,
    on_lifecycle,
    exec_runner,
    suspend_runner,
  }
}

///|
/// Install an opt-in structured view while preserving the plain string view as
/// a fallback for callers that do not need per-frame terminal state.
pub fn[Model, Msg] Program::with_structured_view(
  self : Program[Model, Msg],
  structured_view : (Model) -> View,
) -> Program[Model, Msg] {
  { ..self, structured_view: Some(structured_view) }
}

///|
pub fn[Model, Msg] Program::with_alt_screen(
  self : Program[Model, Msg],
  enabled : Bool,
) -> Program[Model, Msg] {
  { ..self, alt_screen: enabled }
}

///|
pub fn[Model, Msg] Program::with_focus_reporting(
  self : Program[Model, Msg],
  enabled : Bool,
) -> Program[Model, Msg] {
  { ..self, focus_reporting: enabled }
}

///|
pub fn[Model, Msg] Program::with_bracketed_paste(
  self : Program[Model, Msg],
  enabled : Bool,
) -> Program[Model, Msg] {
  { ..self, bracketed_paste: enabled }
}

///|
pub fn[Model, Msg] Program::with_hide_cursor(
  self : Program[Model, Msg],
  enabled : Bool,
) -> Program[Model, Msg] {
  { ..self, hide_cursor: enabled }
}

///|
pub fn[Model, Msg] Program::with_synchronized_output(
  self : Program[Model, Msg],
  enabled : Bool,
) -> Program[Model, Msg] {
  { ..self, synchronized_output: enabled }
}

///|
pub fn[Model, Msg] Program::with_rendering(
  self : Program[Model, Msg],
  enabled : Bool,
) -> Program[Model, Msg] {
  { ..self, rendering: enabled }
}

///|
pub fn[Model, Msg] Program::with_stdin(
  self : Program[Model, Msg],
  enabled : Bool,
) -> Program[Model, Msg] {
  { ..self, read_stdin: enabled }
}

///|
pub fn[Model, Msg] Program::with_input(
  self : Program[Model, Msg],
  input : () -> Array[InputEvent],
) -> Program[Model, Msg] {
  { ..self, input: Some(input), read_stdin: false }
}

///|
pub fn[Model, Msg] Program::with_raw_input(
  self : Program[Model, Msg],
  raw_input : () -> Bytes,
) -> Program[Model, Msg] {
  { ..self, raw_input: Some(raw_input), read_stdin: false }
}

///|
pub fn[Model, Msg] Program::with_cancelled(
  self : Program[Model, Msg],
  cancelled : () -> Bool,
) -> Program[Model, Msg] {
  { ..self, cancelled: Some(cancelled) }
}

///|
pub fn[Model, Msg] Program::with_fps(
  self : Program[Model, Msg],
  fps : Int,
) -> Program[Model, Msg] {
  { ..self, fps, }
}

///|
pub fn[Model, Msg] Program::with_window_size(
  self : Program[Model, Msg],
  width : Int,
  height : Int,
) -> Program[Model, Msg] {
  {
    ..self,
    window_width: @internal.clamp_non_negative(width),
    window_height: @internal.clamp_non_negative(height),
  }
}

///|
pub fn[Model, Msg] Program::with_output(
  self : Program[Model, Msg],
  output : (String) -> Unit,
) -> Program[Model, Msg] {
  { ..self, output, }
}

///|
pub fn[Model, Msg] Program::headless(
  self : Program[Model, Msg],
) -> Program[Model, Msg] {
  {
    ..self,
    mouse: false,
    alt_screen: false,
    focus_reporting: false,
    bracketed_paste: false,
    hide_cursor: false,
    rendering: false,
    read_stdin: false,
  }
}

///|
pub fn[Model, Msg] Program::with_catch_interrupt(
  self : Program[Model, Msg],
  enabled : Bool,
) -> Program[Model, Msg] {
  { ..self, catch_interrupt: enabled }
}

///|
pub fn[Model, Msg] Program::with_lifecycle(
  self : Program[Model, Msg],
  on_lifecycle : (LifecycleMsg) -> Msg?,
) -> Program[Model, Msg] {
  { ..self, on_lifecycle, }
}

///|
pub fn[Model, Msg] Program::with_exec_runner(
  self : Program[Model, Msg],
  exec_runner : (ExecProcess) -> ExecResult,
) -> Program[Model, Msg] {
  { ..self, exec_runner, }
}

///|
pub fn[Model, Msg] Program::with_suspend_runner(
  self : Program[Model, Msg],
  suspend_runner : () -> Unit,
) -> Program[Model, Msg] {
  { ..self, suspend_runner, }
}

///|
#cfg(any(target="native", target="llvm"))
fn append_exec_arg(argv : Array[Byte], value : String) -> Bool {
  let bytes = @utf8.encode(value)
  for byte in bytes.to_array() {
    if byte == b'\x00' {
      return false
    }
    argv.push(byte)
  }
  argv.push(b'\x00')
  true
}

///|
#cfg(any(target="native", target="llvm"))
fn default_exec_runner(process : ExecProcess) -> ExecResult {
  if process.command == "" {
    return ExecError("empty command")
  }
  let argv : Array[Byte] = []
  if !append_exec_arg(argv, process.command) {
    return ExecError("command contains NUL byte")
  }
  for arg in process.args {
    if !append_exec_arg(argv, arg) {
      return ExecError("argument contains NUL byte")
    }
  }
  let buf = Bytes::from_array(argv)
  let code = pippa_exec_process(buf, buf.length(), process.args.length() + 1)
  if code >= 0 {
    ExecCompleted(code)
  } else if code == -2 {
    ExecCancelled
  } else if code <= -2000 {
    ExecSignaled(-2000 - code)
  } else if code <= -1000 {
    ExecError("failed to exec " + process.command)
  } else {
    ExecError("failed to run process: " + process.command)
  }
}

///|
#cfg(not(any(target="native", target="llvm")))
fn default_exec_runner(process : ExecProcess) -> ExecResult {
  ExecError("external process execution is unavailable: " + process.command)
}

///|
#cfg(any(target="native", target="llvm"))
fn default_suspend_runner() -> Unit {
  pippa_suspend_process()
}

///|
#cfg(not(any(target="native", target="llvm")))
fn default_suspend_runner() -> Unit {

}

///|
#cfg(any(target="native", target="llvm"))
fn default_output(output : String) -> Unit {
  if output != "" {
    let bytes = @utf8.encode(output)
    pippa_write_bytes(bytes, bytes.length())
  }
}

///|
#cfg(not(any(target="native", target="llvm")))
fn default_output(_output : String) -> Unit {

}

///|
#cfg(any(target="native", target="llvm"))
let runtime_clock_override : Ref[(() -> Int64)?] = Ref(None)

///|
#cfg(any(target="native", target="llvm"))
#warnings("-unused_value")
fn restore_runtime_clock(previous : (() -> Int64)?) -> Unit {
  runtime_clock_override.val = previous
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_now_ms() -> Int64 {
  match runtime_clock_override.val {
    Some(now) => now()
    None => pippa_now_ms()
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] emit_output(
  program : Program[Model, Msg],
  output : String,
) -> Unit {
  if output != "" {
    (program.output)(output)
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] program_enter_sequence(program : Program[Model, Msg]) -> String {
  program_enter_sequence_with_modes(
    program,
    program.alt_screen,
    program.hide_cursor,
    program.read_stdin && program.focus_reporting,
    program.read_stdin && program.bracketed_paste,
  )
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] program_enter_sequence_with_modes(
  program : Program[Model, Msg],
  alt_screen : Bool,
  cursor_hidden : Bool,
  focus_reporting : Bool,
  bracketed_paste : Bool,
) -> String {
  let buf = StringBuilder::new()
  if program.rendering {
    if alt_screen {
      buf.write_string(enter_alt_screen())
    }
    if cursor_hidden {
      buf.write_string(hide_cursor())
    }
    buf.write_string(clear_screen())
    buf.write_string(move_cursor(1, 1))
  }
  if focus_reporting {
    buf.write_string(enable_focus_reporting())
  }
  if bracketed_paste {
    buf.write_string(enable_bracketed_paste())
  }
  buf.to_string()
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] program_exit_sequence(program : Program[Model, Msg]) -> String {
  program_exit_sequence_with_modes(
    program,
    program.alt_screen,
    program.hide_cursor,
    program.read_stdin && program.focus_reporting,
    program.read_stdin && program.bracketed_paste,
  )
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] program_exit_sequence_with_modes(
  program : Program[Model, Msg],
  alt_screen : Bool,
  cursor_hidden : Bool,
  focus_reporting : Bool,
  bracketed_paste : Bool,
) -> String {
  let buf = StringBuilder::new()
  if focus_reporting {
    buf.write_string(disable_focus_reporting())
  }
  if bracketed_paste {
    buf.write_string(disable_bracketed_paste())
  }
  if program.rendering {
    if alt_screen {
      buf.write_string(exit_alt_screen())
    }
    ignore(cursor_hidden)
    buf.write_string(terminal_view_reset_sequence())
  }
  buf.to_string()
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] activate_terminal(program : Program[Model, Msg]) -> Unit {
  if program.read_stdin {
    pippa_enter_raw_mode()
  }
  let enter_seq = program_enter_sequence(program)
  emit_output(program, enter_seq)
  if program.rendering && program.read_stdin && program.mouse {
    emit_output(program, enable_mouse())
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] activate_terminal_with_modes(
  program : Program[Model, Msg],
  alt_screen : Bool,
  cursor_hidden : Bool,
  focus_reporting : Bool,
  bracketed_paste : Bool,
) -> Unit {
  if program.read_stdin {
    pippa_enter_raw_mode()
  }
  let enter_seq = program_enter_sequence_with_modes(
    program, alt_screen, cursor_hidden, focus_reporting, bracketed_paste,
  )
  emit_output(program, enter_seq)
  if program.rendering && program.read_stdin && program.mouse {
    emit_output(program, enable_mouse())
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] deactivate_terminal(program : Program[Model, Msg]) -> Unit {
  let exit_seq = program_exit_sequence(program)
  emit_output(program, exit_seq)
  if program.read_stdin {
    pippa_exit_raw_mode()
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] deactivate_terminal_with_modes(
  program : Program[Model, Msg],
  alt_screen : Bool,
  cursor_hidden : Bool,
  focus_reporting : Bool,
  bracketed_paste : Bool,
) -> Unit {
  let exit_seq = program_exit_sequence_with_modes(
    program, alt_screen, cursor_hidden, focus_reporting, bracketed_paste,
  )
  emit_output(program, exit_seq)
  if program.read_stdin {
    pippa_exit_raw_mode()
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] render_fps(program : Program[Model, Msg]) -> Int64 {
  program.fps.max(1).min(120).to_int64()
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] render_frame_deadline_ms(
  program : Program[Model, Msg],
  frame_index : Int64,
) -> Int64 {
  let fps = render_fps(program)
  (frame_index * 1000L + fps - 1L) / fps
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] render_frame_index_at_or_before(
  program : Program[Model, Msg],
  timestamp_ms : Int64,
) -> Int64 {
  timestamp_ms.max(0L) * render_fps(program) / 1000L
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] render_cadence_mark_at_or_before(
  program : Program[Model, Msg],
  timestamp_ms : Int64,
) -> Int64 {
  render_frame_deadline_ms(
    program,
    render_frame_index_at_or_before(program, timestamp_ms),
  )
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] next_render_deadline_after(
  program : Program[Model, Msg],
  last_render_ms : Int64,
) -> Int64 {
  let mut frame_index = render_frame_index_at_or_before(program, last_render_ms) +
    1L
  let mut deadline = render_frame_deadline_ms(program, frame_index)
  while deadline <= last_render_ms {
    frame_index = frame_index + 1L
    deadline = render_frame_deadline_ms(program, frame_index)
  }
  deadline
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] maybe_initial_window_size(
  program : Program[Model, Msg],
) -> WindowSize? {
  if program.window_width <= 0 && program.window_height <= 0 {
    None
  } else {
    Some({
      width: if program.window_width > 0 {
        program.window_width
      } else {
        pippa_get_cols()
      },
      height: if program.window_height > 0 {
        program.window_height
      } else {
        pippa_get_rows()
      },
    })
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn current_window_size() -> WindowSize {
  { width: pippa_get_cols(), height: pippa_get_rows() }
}

///|
/// Run the program.
///
/// On `native` and `llvm` targets this enters raw terminal mode and drives
/// the main event loop. On other targets this initializes the model, exposes
/// an already-closed handle to `run_with_handle`, and returns `RunCompleted`.
pub fn[Model, Msg] Program::run(
  program : Program[Model, Msg],
) -> RunResult[Model] {
  run_internal(program, fn(_handle) { () })
}

///|
/// Run the program and expose a handle that can inject messages while it is
/// running.
pub fn[Model, Msg] Program::run_with_handle(
  program : Program[Model, Msg],
  on_start : (ProgramHandle[Msg]) -> Unit,
) -> RunResult[Model] {
  run_internal(program, on_start)
}

///|
#cfg(not(any(target="native", target="llvm")))
fn[Model, Msg] run_internal(
  program : Program[Model, Msg],
  on_start : (ProgramHandle[Msg]) -> Unit,
) -> RunResult[Model] {
  let init_res = (program.init)()
  on_start(
    ProgramHandle::new(
      @queue.Queue([]),
      @queue.Queue([]),
      fn() { () },
      Ref(true),
    ),
  )
  RunCompleted(init_res.model)
}

///|
/// Active one-shot timer managed by the runtime.
#cfg(any(target="native", target="llvm"))
priv enum RuntimeTimer[Msg] {
  TickAt(Int64)
  AfterAt(Int64, () -> Msg?)
  EveryAt(Int64, Int64, (TimerTick) -> Msg)
}

///|
#cfg(any(target="native", target="llvm"))
priv enum RuntimeScreenCommand {
  RuntimeEnterAltScreen
  RuntimeExitAltScreen
  RuntimeClearScreen
  RuntimeRepaint
  RuntimeForceRedraw
  RuntimeHideCursor
  RuntimeShowCursor
}

///|
#cfg(any(target="native", target="llvm"))
priv enum RuntimeTerminalCommand {
  RuntimeRequestDeviceAttributes
  RuntimeRequestSecondaryDeviceAttributes
  RuntimeRequestCursorPosition
  RuntimeRequestForegroundColor
  RuntimeRequestBackgroundColor
  RuntimeRequestCapability(String)
  RuntimeRequestClipboard(ClipboardSelection)
  RuntimeSetClipboard(ClipboardSelection, String)
  RuntimeSetFocusReporting(Bool)
  RuntimeSetBracketedPaste(Bool)
  RuntimeSetMouseMode(ViewMouseMode)
  RuntimeSetWindowTitle(String)
  RuntimeSetCursorStyle(CursorStyle)
}

///|
#cfg(any(target="native", target="llvm"))
priv enum RuntimeEffect[Msg] {
  RuntimeTask(() -> Msg?)
  RuntimeScreen(RuntimeScreenCommand)
  RuntimeTerminal(RuntimeTerminalCommand)
  RuntimeLifecycle(LifecycleMsg)
  RuntimeExec(ExecProcess, (ExecResult) -> Msg)
  RuntimeError(String)
}

///|
#cfg(any(target="native", target="llvm"))
let runtime_print_above_messages : Ref[Array[PrintAboveMessage]] = Ref([])

///|
#cfg(any(target="native", target="llvm"))
fn runtime_enqueue_print_above(message : PrintAboveMessage) -> Unit {
  runtime_print_above_messages.val.push(message)
}

///|
#cfg(not(any(target="native", target="llvm")))
fn runtime_enqueue_print_above(message : PrintAboveMessage) -> Unit {
  ignore(message.message_body)
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_take_print_above_messages() -> Array[PrintAboveMessage] {
  let messages = runtime_print_above_messages.val
  runtime_print_above_messages.val = []
  messages
}

///|
#cfg(any(target="native", target="llvm"))
priv enum RuntimeExitReason {
  RuntimeExitCompleted
  RuntimeExitQuit
  RuntimeExitInterrupted
  RuntimeExitError(String)
  RuntimeExitExec(ExecResult)
}

///|
#cfg(any(target="native", target="llvm"))
priv struct RuntimeRenderState {
  terminal_active : Bool
  alt_screen : Bool
  cursor_hidden : Bool
  focus_reporting : Bool
  bracketed_paste : Bool
  rendered : String
  prev_line_count : Int
  terminal_height : Int
  last_render_ms : Int64
  render_due_at : Int64?
  force_full_repaint : Bool
  render_context : @profile.RenderContext
  view_terminal_state : RuntimeViewTerminalState
}

///|
#cfg(any(target="native", target="llvm"))
priv struct RuntimeTerminalProgress {
  state : Int
  value : Int
} derive(Eq)

///|
#cfg(any(target="native", target="llvm"))
priv struct RuntimeViewTerminalState {
  cursor_position : ViewCursorPosition?
  cursor_visible : Bool?
  window_title : String?
  cursor_style : CursorStyle?
  foreground_color : Color?
  background_color : Color?
  render_context : @profile.RenderContext
  progress : RuntimeTerminalProgress?
  mouse_mode : ViewMouseMode?
} derive(Eq)

///|
#cfg(any(target="native", target="llvm"))
fn runtime_view_terminal_state_empty() -> RuntimeViewTerminalState {
  {
    cursor_position: None,
    cursor_visible: None,
    window_title: None,
    cursor_style: None,
    foreground_color: None,
    background_color: None,
    render_context: @profile.RenderContext::default(),
    progress: None,
    mouse_mode: None,
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] runtime_view_terminal_state_for_program(
  program : Program[Model, Msg],
  terminal_active : Bool,
  ctx : @profile.RenderContext,
) -> RuntimeViewTerminalState {
  let state = { ..runtime_view_terminal_state_empty(), render_context: ctx }
  {
    ..state,
    cursor_visible: if terminal_active &&
      program.rendering &&
      program.hide_cursor {
      Some(false)
    } else {
      None
    },
    mouse_mode: if terminal_active &&
      program.rendering &&
      program.read_stdin &&
      program.mouse {
      Some(ViewMousePress)
    } else {
      None
    },
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Msg] timer_deadline(timer : RuntimeTimer[Msg]) -> Int64 {
  match timer {
    TickAt(deadline) => deadline
    AfterAt(deadline, _) => deadline
    EveryAt(deadline, _, _) => deadline
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] initial_render_state(
  program : Program[Model, Msg],
  terminal_active : Bool,
) -> RuntimeRenderState {
  let render_context = @profile.RenderContext::detect()
  @profile.set_render_context(render_context)
  {
    terminal_active,
    alt_screen: program.alt_screen,
    cursor_hidden: program.hide_cursor,
    focus_reporting: terminal_active &&
    program.read_stdin &&
    program.focus_reporting,
    bracketed_paste: terminal_active &&
    program.read_stdin &&
    program.bracketed_paste,
    rendered: "",
    prev_line_count: 0,
    terminal_height: current_window_size().height.max(1),
    last_render_ms: 0L,
    render_due_at: None,
    force_full_repaint: false,
    render_context,
    view_terminal_state: runtime_view_terminal_state_for_program(
      program, terminal_active, render_context,
    ),
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] apply_screen_command(
  program : Program[Model, Msg],
  state : RuntimeRenderState,
  command : RuntimeScreenCommand,
) -> RuntimeRenderState {
  match command {
    RuntimeEnterAltScreen =>
      if state.alt_screen {
        state
      } else {
        if state.terminal_active && program.rendering {
          emit_output(program, enter_alt_screen())
        }
        {
          ..state,
          alt_screen: true,
          rendered: "",
          prev_line_count: 0,
          render_due_at: None,
        }
      }
    RuntimeExitAltScreen =>
      if !state.alt_screen {
        state
      } else {
        if state.terminal_active && program.rendering {
          emit_output(program, exit_alt_screen())
        }
        {
          ..state,
          alt_screen: false,
          rendered: "",
          prev_line_count: 0,
          render_due_at: None,
        }
      }
    RuntimeClearScreen => {
      if state.terminal_active && program.rendering {
        emit_output(program, clear_screen() + move_cursor(1, 1))
      }
      {
        ..state,
        rendered: "",
        prev_line_count: 0,
        render_due_at: None,
        view_terminal_state: {
          ..state.view_terminal_state,
          cursor_position: None,
        },
      }
    }
    RuntimeRepaint =>
      {
        ..state,
        force_full_repaint: true,
        render_due_at: None,
        view_terminal_state: {
          ..state.view_terminal_state,
          cursor_position: None,
        },
      }
    RuntimeForceRedraw =>
      {
        ..state,
        force_full_repaint: true,
        render_due_at: None,
        view_terminal_state: {
          ..state.view_terminal_state,
          cursor_position: None,
        },
      }
    RuntimeHideCursor =>
      if state.cursor_hidden {
        {
          ..state,
          view_terminal_state: {
            ..state.view_terminal_state,
            cursor_visible: Some(false),
          },
        }
      } else {
        if state.terminal_active && program.rendering {
          emit_output(program, hide_cursor())
        }
        {
          ..state,
          cursor_hidden: true,
          view_terminal_state: {
            ..state.view_terminal_state,
            cursor_visible: Some(false),
          },
        }
      }
    RuntimeShowCursor =>
      if !state.cursor_hidden {
        {
          ..state,
          view_terminal_state: {
            ..state.view_terminal_state,
            cursor_visible: Some(true),
          },
        }
      } else {
        if state.terminal_active && program.rendering {
          emit_output(program, show_cursor())
        }
        {
          ..state,
          cursor_hidden: false,
          view_terminal_state: {
            ..state.view_terminal_state,
            cursor_visible: Some(true),
          },
        }
      }
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn terminal_command_sequence(command : RuntimeTerminalCommand) -> String {
  match command {
    RuntimeRequestDeviceAttributes => request_device_attributes()
    RuntimeRequestSecondaryDeviceAttributes =>
      request_secondary_device_attributes()
    RuntimeRequestCursorPosition => request_cursor_position()
    RuntimeRequestForegroundColor => request_foreground_color()
    RuntimeRequestBackgroundColor => request_background_color()
    RuntimeRequestCapability(name) => request_capability(name)
    RuntimeRequestClipboard(selection) => request_clipboard(selection)
    RuntimeSetClipboard(selection, data) => set_clipboard(selection, data)
    RuntimeSetFocusReporting(true) => enable_focus_reporting()
    RuntimeSetFocusReporting(false) => disable_focus_reporting()
    RuntimeSetBracketedPaste(true) => enable_bracketed_paste()
    RuntimeSetBracketedPaste(false) => disable_bracketed_paste()
    RuntimeSetMouseMode(ViewMousePress) => enable_mouse()
    RuntimeSetMouseMode(ViewMouseCellMotion) => enable_mouse_cell_motion()
    RuntimeSetMouseMode(ViewMouseAllMotion) => enable_mouse_motion()
    RuntimeSetMouseMode(ViewMouseOff) => disable_mouse()
    RuntimeSetWindowTitle(title) => set_window_title(title)
    RuntimeSetCursorStyle(style) => set_cursor_style(style)
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_window_title_state(title : String) -> String? {
  if title == "" {
    None
  } else {
    Some(title)
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_cursor_style_state(style : CursorStyle) -> CursorStyle? {
  Some(style)
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] apply_terminal_command(
  program : Program[Model, Msg],
  state : RuntimeRenderState,
  command : RuntimeTerminalCommand,
) -> RuntimeRenderState {
  if state.terminal_active {
    let sequence = match command {
      RuntimeSetMouseMode(mode) =>
        mouse_mode_transition(state.view_terminal_state.mouse_mode, Some(mode))
      _ => terminal_command_sequence(command)
    }
    emit_output(program, sequence)
  }
  match command {
    RuntimeSetFocusReporting(enabled) => { ..state, focus_reporting: enabled }
    RuntimeSetBracketedPaste(enabled) => { ..state, bracketed_paste: enabled }
    RuntimeSetMouseMode(mode) =>
      {
        ..state,
        view_terminal_state: {
          ..state.view_terminal_state,
          mouse_mode: view_mouse_state(Some(mode)),
        },
      }
    RuntimeSetWindowTitle(title) =>
      {
        ..state,
        view_terminal_state: {
          ..state.view_terminal_state,
          window_title: runtime_window_title_state(title),
        },
      }
    RuntimeSetCursorStyle(style) =>
      {
        ..state,
        view_terminal_state: {
          ..state.view_terminal_state,
          cursor_style: runtime_cursor_style_state(style),
        },
      }
    _ => state
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_hex_value(ch : Char) -> Int? {
  if ch >= '0' && ch <= '9' {
    Some(ch.to_int() - '0'.to_int())
  } else if ch >= 'a' && ch <= 'f' {
    Some(ch.to_int() - 'a'.to_int() + 10)
  } else if ch >= 'A' && ch <= 'F' {
    Some(ch.to_int() - 'A'.to_int() + 10)
  } else {
    None
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_parse_rgb_component(part : StringView) -> Int? {
  let width = part.length()
  if width < 1 || width > 4 {
    return None
  }
  let mut value = 0
  for ch in part {
    match runtime_hex_value(ch) {
      Some(digit) => value = value * 16 + digit
      None => return None
    }
  }
  let max_value = (1 << (width * 4)) - 1
  Some(value * 255 / max_value)
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_background_from_osc11(payload : String) -> @profile.Background? {
  if !payload.has_prefix("rgb:") {
    return None
  }
  let parts : Array[StringView] = payload[:][4:].split("/").collect()
  if parts.length() != 3 {
    return None
  }
  let r = match runtime_parse_rgb_component(parts[0]) {
    Some(value) => value
    None => return None
  }
  let g = match runtime_parse_rgb_component(parts[1]) {
    Some(value) => value
    None => return None
  }
  let b = match runtime_parse_rgb_component(parts[2]) {
    Some(value) => value
    None => return None
  }
  let luminance = 0.2126 * (r.to_double() / 255.0) +
    0.7152 * (g.to_double() / 255.0) +
    0.0722 * (b.to_double() / 255.0)
  if luminance > 0.5 {
    Some(@profile.Light)
  } else {
    Some(@profile.Dark)
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_profile_rank(profile : @profile.ColorProfile) -> Int {
  match profile {
    NoColor => 0
    Ansi16 => 1
    Ansi256 => 2
    TrueColor => 3
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_max_profile(
  current : @profile.ColorProfile,
  floor : @profile.ColorProfile,
) -> @profile.ColorProfile {
  if runtime_profile_rank(current) >= runtime_profile_rank(floor) {
    current
  } else {
    floor
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_context_apply_capability(
  ctx : @profile.RenderContext,
  reply : CapabilityReply,
) -> @profile.RenderContext {
  if !reply.valid {
    return ctx
  }
  match reply.name {
    "Tc" | "RGB" => ctx.with_color_profile(@profile.TrueColor)
    "Co" =>
      match reply.value {
        Some(value) => {
          let colors = @string.parse_int(value[:], base=10) catch {
            _ => return ctx
          }
          if colors >= 256 {
            ctx.with_color_profile(
              runtime_max_profile(ctx.color_profile, @profile.Ansi256),
            )
          } else if colors >= 8 {
            ctx.with_color_profile(
              runtime_max_profile(ctx.color_profile, @profile.Ansi16),
            )
          } else {
            ctx
          }
        }
        None => ctx
      }
    _ => ctx
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_context_apply_event(
  ctx : @profile.RenderContext,
  event : InputEvent,
) -> @profile.RenderContext {
  match event {
    TerminalReply(Color(BackgroundColor(payload))) =>
      match runtime_background_from_osc11(payload) {
        Some(background) => ctx.with_background(background)
        None => ctx
      }
    TerminalReply(Capability(reply)) =>
      runtime_context_apply_capability(ctx, reply)
    _ => ctx
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_apply_render_context_events(
  state : RuntimeRenderState,
  events : Array[InputEvent],
) -> (RuntimeRenderState, Bool) {
  let original = state.render_context
  let mut ctx = original
  for event in events {
    ctx = runtime_context_apply_event(ctx, event)
  }
  if ctx == original {
    (state, false)
  } else {
    @profile.set_render_context(ctx)
    ({ ..state, render_context: ctx, force_full_repaint: true }, true)
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn progress_value(value : Int?) -> Int {
  match value {
    Some(n) => n.max(0).min(100)
    None => 0
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn view_progress_state(progress : ViewProgress?) -> RuntimeTerminalProgress? {
  match progress {
    None | Some(ViewProgressNone) => None
    Some(ViewProgressIndeterminate) => Some({ state: 3, value: 0 })
    Some(ViewProgressPercent(value)) =>
      Some({ state: 1, value: progress_value(Some(value)) })
    Some(ViewProgressPaused(value)) =>
      Some({ state: 4, value: progress_value(value) })
    Some(ViewProgressError(value)) =>
      Some({ state: 2, value: progress_value(value) })
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn view_progress_sequence(progress : RuntimeTerminalProgress) -> String {
  set_terminal_progress(progress.state, progress.value)
}

///|
#cfg(any(target="native", target="llvm"))
fn view_mouse_state(mouse_mode : ViewMouseMode?) -> ViewMouseMode? {
  match mouse_mode {
    Some(ViewMousePress) => Some(ViewMousePress)
    Some(ViewMouseCellMotion) => Some(ViewMouseCellMotion)
    Some(ViewMouseAllMotion) => Some(ViewMouseAllMotion)
    Some(ViewMouseOff) | None => None
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn mouse_mode_transition(
  previous : ViewMouseMode?,
  next : ViewMouseMode?,
) -> String {
  match next {
    None => disable_mouse()
    Some(ViewMouseOff) => disable_mouse()
    Some(ViewMousePress) =>
      match previous {
        Some(ViewMouseCellMotion) => "\{CSI}?1002l" + enable_mouse()
        Some(ViewMouseAllMotion) => "\{CSI}?1003l" + enable_mouse()
        _ => enable_mouse()
      }
    Some(ViewMouseCellMotion) =>
      match previous {
        Some(ViewMousePress) => "\{CSI}?1000l" + enable_mouse_cell_motion()
        Some(ViewMouseAllMotion) => "\{CSI}?1003l" + enable_mouse_cell_motion()
        _ => enable_mouse_cell_motion()
      }
    Some(ViewMouseAllMotion) =>
      match previous {
        Some(ViewMousePress) => "\{CSI}?1000l" + enable_mouse_motion()
        Some(ViewMouseCellMotion) => "\{CSI}?1002l" + enable_mouse_motion()
        _ => enable_mouse_motion()
      }
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn view_terminal_state_from_view(
  view : View,
  ctx : @profile.RenderContext,
) -> RuntimeViewTerminalState {
  {
    cursor_position: view.cursor_position,
    cursor_visible: view.cursor_visible,
    window_title: view.window_title,
    cursor_style: view.cursor_style,
    foreground_color: view.foreground_color,
    background_color: view.background_color,
    render_context: ctx,
    progress: view_progress_state(view.progress),
    mouse_mode: view_mouse_state(view.mouse_mode),
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn view_terminal_state_delta(
  applied : RuntimeViewTerminalState,
  view : View,
  ctx : @profile.RenderContext,
) -> (String, RuntimeViewTerminalState) {
  let next = view_terminal_state_from_view(view, ctx)
  let context_changed = ctx != applied.render_context
  let prefix = StringBuilder::new()
  if next.window_title != applied.window_title {
    match next.window_title {
      Some(title) => prefix.write_string(set_window_title(title))
      None =>
        if applied.window_title is Some(_) {
          prefix.write_string(clear_window_title())
        }
    }
  }
  if next.cursor_style != applied.cursor_style {
    match next.cursor_style {
      Some(style) => prefix.write_string(set_cursor_style(style))
      None =>
        match applied.cursor_style {
          Some(Default) | None => ()
          Some(_) => prefix.write_string(set_cursor_style(Default))
        }
    }
  }
  if next.foreground_color != applied.foreground_color ||
    (context_changed && next.foreground_color is Some(_)) {
    match next.foreground_color {
      Some(color) => prefix.write_string(foreground_sequence(ctx, color))
      None =>
        if applied.foreground_color is Some(_) {
          prefix.write_string(reset_foreground_color())
        }
    }
  }
  if next.background_color != applied.background_color ||
    (context_changed && next.background_color is Some(_)) {
    match next.background_color {
      Some(color) => prefix.write_string(background_sequence(ctx, color))
      None =>
        if applied.background_color is Some(_) {
          prefix.write_string(reset_background_color())
        }
    }
  }
  if next.progress != applied.progress {
    match next.progress {
      Some(progress) => prefix.write_string(view_progress_sequence(progress))
      None =>
        if applied.progress is Some(_) {
          prefix.write_string(clear_terminal_progress())
        }
    }
  }
  if next.mouse_mode != applied.mouse_mode {
    prefix.write_string(
      mouse_mode_transition(applied.mouse_mode, next.mouse_mode),
    )
  }
  if next.cursor_visible != applied.cursor_visible {
    match next.cursor_visible {
      Some(true) => prefix.write_string(show_cursor())
      Some(false) => prefix.write_string(hide_cursor())
      None => ()
    }
  }
  (prefix.to_string(), next)
}

///|
#cfg(any(target="native", target="llvm"))
fn view_cursor_position_suffix(
  applied : RuntimeViewTerminalState,
  next : RuntimeViewTerminalState,
  content_painted : Bool,
) -> String {
  if next.cursor_position != applied.cursor_position {
    match next.cursor_position {
      Some(pos) => move_cursor(pos.row, pos.col)
      None => ""
    }
  } else if content_painted {
    match next.cursor_position {
      Some(pos) => move_cursor(pos.row, pos.col)
      None => ""
    }
  } else {
    ""
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn terminal_view_reset_sequence() -> String {
  show_cursor() +
  set_cursor_style(Default) +
  clear_window_title() +
  reset_foreground_color() +
  reset_background_color() +
  clear_terminal_progress() +
  disable_mouse()
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_print_above_lines(
  messages : Array[PrintAboveMessage],
) -> Array[String] {
  let lines : Array[String] = []
  for message in messages {
    if message.message_body != "" {
      for line in split_lines(message.message_body) {
        lines.push(line)
      }
    }
  }
  lines
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_print_above_frame_output(
  lines : Array[String],
  prev_line_count : Int,
  current_content : String,
  terminal_height : Int,
) -> String {
  let buf = StringBuilder::new()
  buf.write_string(render_full("", prev_line_count))
  buf.write_string(move_cursor(terminal_height.max(1), 1))
  for line in lines {
    buf.write_string(line)
    buf.write_string(clear_line())
    buf.write_string("\r\n")
  }
  buf.write_string(render_full(current_content, 0))
  buf.to_string()
}

///|
#cfg(any(target="native", target="llvm"))
fn runtime_render_state_with_window_size(
  state : RuntimeRenderState,
  size : WindowSize,
) -> RuntimeRenderState {
  { ..state, terminal_height: size.height.max(1) }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] apply_print_above_messages(
  program : Program[Model, Msg],
  state : RuntimeRenderState,
  model : Model,
) -> RuntimeRenderState {
  let lines = runtime_print_above_lines(runtime_take_print_above_messages())
  if lines.is_empty() ||
    !state.terminal_active ||
    !program.rendering ||
    state.alt_screen {
    state
  } else {
    let current_view = desired_view(program, model)
    let (terminal_prefix, next_terminal_state) = view_terminal_state_delta(
      state.view_terminal_state,
      current_view,
      state.render_context,
    )
    let frame_output = runtime_print_above_frame_output(
      lines,
      state.prev_line_count,
      current_view.content,
      state.terminal_height,
    )
    let terminal_suffix = view_cursor_position_suffix(
      state.view_terminal_state,
      next_terminal_state,
      true,
    )
    let output = terminal_prefix + frame_output + terminal_suffix
    let output = if program.synchronized_output && output != "" {
      begin_sync_update() + output + end_sync_update()
    } else {
      output
    }
    emit_output(program, output)
    let now = runtime_now_ms()
    {
      ..state,
      rendered: current_view.content,
      prev_line_count: line_count(current_view.content),
      last_render_ms: render_cadence_mark_at_or_before(program, now),
      render_due_at: None,
      force_full_repaint: false,
      view_terminal_state: next_terminal_state,
    }
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn next_aligned_every_deadline(now : Int64, interval_ms : Int) -> Int64 {
  let interval = interval_ms.to_int64()
  let elapsed = now.max(0L)
  (elapsed / interval + 1L) * interval
}

///|
#cfg(any(target="native", target="llvm"))
fn[Msg] queue_cmd_effects(
  cmd : Cmd[Msg],
  now : Int64,
  pending : @queue.Queue[RuntimeEffect[Msg]],
  timers : Array[RuntimeTimer[Msg]],
  sequences : @deque.Deque[Array[Cmd[Msg]]],
) -> Unit {
  for action in cmd.actions {
    match action {
      Perform(task) => pending.push(RuntimeTask(task))
      Tick(interval_ms) =>
        if interval_ms > 0 {
          timers.push(TickAt(now + interval_ms.to_int64()))
        }
      After(interval_ms, task) => {
        let ms = if interval_ms > 0 { interval_ms } else { 1 }
        timers.push(AfterAt(now + ms.to_int64(), fn() { Some(task()) }))
      }
      Every(interval_ms, task) => {
        let ms = if interval_ms > 0 { interval_ms } else { 1 }
        timers.push(EveryAt(next_aligned_every_deadline(now, ms), now, task))
      }
      EnterAltScreen => pending.push(RuntimeScreen(RuntimeEnterAltScreen))
      ExitAltScreen => pending.push(RuntimeScreen(RuntimeExitAltScreen))
      ClearScreen => pending.push(RuntimeScreen(RuntimeClearScreen))
      Repaint => pending.push(RuntimeScreen(RuntimeRepaint))
      ForceRedraw => pending.push(RuntimeScreen(RuntimeForceRedraw))
      HideCursor => pending.push(RuntimeScreen(RuntimeHideCursor))
      ShowCursor => pending.push(RuntimeScreen(RuntimeShowCursor))
      RequestWindowSize(task) =>
        pending.push(RuntimeTask(fn() { Some(task(current_window_size())) }))
      RequestDeviceAttributes =>
        pending.push(RuntimeTerminal(RuntimeRequestDeviceAttributes))
      RequestSecondaryDeviceAttributes =>
        pending.push(RuntimeTerminal(RuntimeRequestSecondaryDeviceAttributes))
      RequestCursorPosition =>
        pending.push(RuntimeTerminal(RuntimeRequestCursorPosition))
      RequestForegroundColor =>
        pending.push(RuntimeTerminal(RuntimeRequestForegroundColor))
      RequestBackgroundColor =>
        pending.push(RuntimeTerminal(RuntimeRequestBackgroundColor))
      RequestCapability(name) =>
        pending.push(RuntimeTerminal(RuntimeRequestCapability(name)))
      RequestClipboard(selection) =>
        pending.push(RuntimeTerminal(RuntimeRequestClipboard(selection)))
      SetClipboard(selection, text) =>
        pending.push(RuntimeTerminal(RuntimeSetClipboard(selection, text)))
      SetFocusReporting(enabled) =>
        pending.push(RuntimeTerminal(RuntimeSetFocusReporting(enabled)))
      SetBracketedPaste(enabled) =>
        pending.push(RuntimeTerminal(RuntimeSetBracketedPaste(enabled)))
      SetMouseMode(mode) =>
        pending.push(RuntimeTerminal(RuntimeSetMouseMode(mode)))
      SetWindowTitle(title) =>
        pending.push(RuntimeTerminal(RuntimeSetWindowTitle(title)))
      SetCursorStyle(style) =>
        pending.push(RuntimeTerminal(RuntimeSetCursorStyle(style)))
      LifecycleAction(msg) => pending.push(RuntimeLifecycle(msg))
      ExecProcessAction(process, to_msg) =>
        pending.push(RuntimeExec(process, to_msg))
      RuntimeErrorAction(message) => pending.push(RuntimeError(message))
      Sequence(cmds) =>
        match cmds {
          [] => ()
          [head, .. tail] => {
            queue_cmd_effects(head, now, pending, timers, sequences)
            if !tail.is_empty() {
              sequences.push_back(tail.to_owned())
            }
          }
        }
    }
  }
}

///|
/// Compute the poll timeout from the earliest timer deadline. Returns -1 if
/// no timers are active (block indefinitely).
#cfg(any(target="native", target="llvm"))
fn[Msg] timers_timeout(timers : Array[RuntimeTimer[Msg]]) -> Int {
  let mut min_deadline : Int64? = None
  for timer in timers {
    match min_deadline {
      None => min_deadline = Some(timer_deadline(timer))
      Some(m) => {
        let deadline = timer_deadline(timer)
        if deadline < m {
          min_deadline = Some(deadline)
        }
      }
    }
  }
  match min_deadline {
    Some(deadline) => {
      let remaining = deadline - runtime_now_ms()
      if remaining < 0L {
        0
      } else {
        remaining.to_int()
      }
    }
    None => -1
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] queue_due_timers(
  program : Program[Model, Msg],
  now : Int64,
  timers : Array[RuntimeTimer[Msg]],
  pending : @queue.Queue[RuntimeEffect[Msg]],
) -> Array[RuntimeTimer[Msg]] {
  let remaining : Array[RuntimeTimer[Msg]] = []
  for timer in timers {
    if timer_deadline(timer) <= now {
      match timer {
        TickAt(_) => pending.push(RuntimeTask(program.on_tick))
        AfterAt(_, task) => pending.push(RuntimeTask(task))
        EveryAt(_, scheduled_at, task) => {
          let elapsed = now - scheduled_at
          pending.push(
            RuntimeTask(fn() { Some(task({ elapsed_ms: elapsed })) }),
          )
        }
      }
    } else {
      remaining.push(timer)
    }
  }
  remaining
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] runtime_timeout_ms(
  program : Program[Model, Msg],
  timers : Array[RuntimeTimer[Msg]],
  render_due_at : Int64?,
) -> Int {
  let mut timeout = timers_timeout(timers)
  match render_due_at {
    Some(deadline) => {
      let remaining = (deadline - runtime_now_ms()).max(0L).to_int()
      if timeout < 0 || remaining < timeout {
        timeout = remaining
      }
    }
    None => ()
  }
  let needs_polling = program.input is Some(_) ||
    program.raw_input is Some(_) ||
    program.cancelled is Some(_)
  if needs_polling && (timeout < 0 || timeout > 16) {
    16
  } else {
    timeout
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] poll_input_events(
  program : Program[Model, Msg],
) -> Array[InputEvent] {
  match program.input {
    Some(input) => input()
    None => []
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] poll_raw_input_events(
  program : Program[Model, Msg],
  leftover : Array[Byte],
) -> (Array[InputEvent], Array[Byte]) {
  match program.raw_input {
    Some(raw_input) => {
      let raw = raw_input()
      if raw.length() == 0 && leftover.is_empty() {
        ([], leftover)
      } else {
        let combined = if leftover.is_empty() {
          raw.to_array()
        } else {
          [..leftover, ..raw.to_array()]
        }
        let bytes = Bytes::from_array(combined)
        let (events, next_leftover) = parse_all(bytes[:])
        (events, next_leftover.to_array())
      }
    }
    None => ([], leftover)
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] is_cancelled(program : Program[Model, Msg]) -> Bool {
  match program.cancelled {
    Some(cancelled) => cancelled()
    None => false
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] apply_window_size(
  program : Program[Model, Msg],
  model : Model,
  size : WindowSize,
  pending : @queue.Queue[RuntimeEffect[Msg]],
  timers : Array[RuntimeTimer[Msg]],
  sequences : @deque.Deque[Array[Cmd[Msg]]],
) -> (Model, RuntimeExitReason?, Bool) {
  let mut current_model = model
  let mut reason : RuntimeExitReason? = None
  let mut updated = false
  match (program.on_window_size)(size) {
    Some(msg) => {
      let (next_model, should_quit, cmd) = apply_msg(
        program, current_model, msg,
      )
      current_model = next_model
      queue_cmd_effects(cmd, runtime_now_ms(), pending, timers, sequences)
      updated = true
      if should_quit {
        reason = Some(RuntimeExitQuit)
      }
    }
    None => ()
  }
  match (program.on_resize)(size.width, size.height) {
    Some(msg) => {
      let (next_model, should_quit, cmd) = apply_msg(
        program, current_model, msg,
      )
      current_model = next_model
      queue_cmd_effects(cmd, runtime_now_ms(), pending, timers, sequences)
      updated = true
      if should_quit {
        reason = Some(RuntimeExitQuit)
      }
    }
    None => ()
  }
  (current_model, reason, updated)
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] apply_initial_window_size(
  program : Program[Model, Msg],
  model : Model,
  pending : @queue.Queue[RuntimeEffect[Msg]],
  timers : Array[RuntimeTimer[Msg]],
  sequences : @deque.Deque[Array[Cmd[Msg]]],
) -> (Model, RuntimeExitReason?, Bool) {
  match maybe_initial_window_size(program) {
    Some(size) =>
      apply_window_size(program, model, size, pending, timers, sequences)
    None => (model, None, false)
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] apply_msg(
  program : Program[Model, Msg],
  model : Model,
  msg : Msg,
) -> (Model, Bool, Cmd[Msg]) {
  let res = (program.update)(model, msg)
  (res.model, (program.should_quit)(res.model), res.cmd)
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] apply_lifecycle_msg(
  program : Program[Model, Msg],
  model : Model,
  msg : LifecycleMsg,
) -> (Model, Bool, Cmd[Msg], Bool) {
  match (program.on_lifecycle)(msg) {
    Some(app_msg) => {
      let (next_model, should_quit, cmd) = apply_msg(program, model, app_msg)
      (next_model, should_quit, cmd, true)
    }
    None => (model, false, Cmd::none(), false)
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Msg] queue_startup_color_detection(
  pending : @queue.Queue[RuntimeEffect[Msg]],
) -> Unit {
  pending.push(RuntimeTerminal(RuntimeRequestBackgroundColor))
  pending.push(RuntimeTerminal(RuntimeRequestCapability("Co")))
  pending.push(RuntimeTerminal(RuntimeRequestCapability("RGB")))
  pending.push(RuntimeTerminal(RuntimeRequestCapability("Tc")))
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model] run_result_from_reason(
  model : Model,
  reason : RuntimeExitReason,
) -> RunResult[Model] {
  match reason {
    RuntimeExitCompleted => RunCompleted(model)
    RuntimeExitQuit => RunQuit(model)
    RuntimeExitInterrupted => RunInterrupted(model)
    RuntimeExitError(message) => RunRuntimeError(message)
    RuntimeExitExec(result) => RunExec(model, result)
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] apply_input_events(
  program : Program[Model, Msg],
  model : Model,
  events : Array[InputEvent],
  pending : @queue.Queue[RuntimeEffect[Msg]],
  timers : Array[RuntimeTimer[Msg]],
  sequences : @deque.Deque[Array[Cmd[Msg]]],
) -> (Model, RuntimeExitReason?, Bool) {
  let mut current_model = model
  let mut reason : RuntimeExitReason? = None
  let mut updated = false
  for evt in events {
    let mut deliver_to_sub = true
    let mut stop_input = false
    match evt {
      Key(Modified("ctrl", "z")) => {
        pending.push(RuntimeLifecycle(Suspend))
        deliver_to_sub = false
        stop_input = true
      }
      _ => ()
    }
    if program.catch_interrupt {
      match evt {
        Key(Modified("ctrl", "c")) => {
          let (next_model, should_quit, cmd, lifecycle_updated) = apply_lifecycle_msg(
            program,
            current_model,
            Interrupt,
          )
          current_model = next_model
          queue_cmd_effects(cmd, runtime_now_ms(), pending, timers, sequences)
          if lifecycle_updated {
            updated = true
          }
          ignore(should_quit)
          reason = Some(RuntimeExitInterrupted)
          deliver_to_sub = false
        }
        _ => ()
      }
    }
    if deliver_to_sub {
      match (program.sub)(evt) {
        Some(msg) => {
          let (next_model, should_quit, cmd) = apply_msg(
            program, current_model, msg,
          )
          current_model = next_model
          queue_cmd_effects(cmd, runtime_now_ms(), pending, timers, sequences)
          updated = true
          if should_quit && reason is None {
            reason = Some(RuntimeExitQuit)
          }
        }
        None => ()
      }
    }
    if reason is Some(_) || stop_input {
      break
    }
  }
  (current_model, reason, updated)
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] release_terminal_state(
  program : Program[Model, Msg],
  state : RuntimeRenderState,
) -> RuntimeRenderState {
  if state.terminal_active {
    if state.alt_screen == program.alt_screen &&
      state.cursor_hidden == program.hide_cursor &&
      state.focus_reporting == (program.read_stdin && program.focus_reporting) &&
      state.bracketed_paste == (program.read_stdin && program.bracketed_paste) {
      deactivate_terminal(program)
    } else {
      deactivate_terminal_with_modes(
        program,
        state.alt_screen,
        state.cursor_hidden,
        state.focus_reporting,
        state.bracketed_paste,
      )
    }
    {
      ..state,
      terminal_active: false,
      rendered: "",
      prev_line_count: 0,
      render_due_at: None,
      force_full_repaint: false,
      view_terminal_state: {
        ..runtime_view_terminal_state_empty(),
        render_context: state.render_context,
      },
    }
  } else {
    state
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] restore_terminal_state(
  program : Program[Model, Msg],
  state : RuntimeRenderState,
  current_view : View,
) -> RuntimeRenderState {
  if !state.terminal_active {
    let mut next_state = state
    if state.alt_screen == program.alt_screen &&
      state.cursor_hidden == program.hide_cursor &&
      state.focus_reporting == (program.read_stdin && program.focus_reporting) &&
      state.bracketed_paste == (program.read_stdin && program.bracketed_paste) {
      activate_terminal(program)
    } else {
      activate_terminal_with_modes(
        program,
        state.alt_screen,
        state.cursor_hidden,
        state.focus_reporting,
        state.bracketed_paste,
      )
    }
    next_state = { ..next_state, terminal_active: true }
    if program.rendering {
      let (
        next_rendered,
        next_line_count,
        next_last_render_ms,
        next_render_due_at,
        next_view_terminal_state,
      ) = flush_view(
        program,
        "",
        current_view,
        0,
        runtime_view_terminal_state_for_program(
          program,
          true,
          state.render_context,
        ),
        true,
        0L,
        true,
        true,
        render_context=state.render_context,
      )
      next_state = {
        ..next_state,
        rendered: next_rendered,
        prev_line_count: next_line_count,
        last_render_ms: next_last_render_ms,
        render_due_at: next_render_due_at,
        force_full_repaint: false,
        view_terminal_state: next_view_terminal_state,
      }
    }
    next_state
  } else {
    state
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] drain_controls(
  program : Program[Model, Msg],
  pending_controls : @queue.Queue[RuntimeControl],
  state : RuntimeRenderState,
  current_view : View,
) -> RuntimeRenderState {
  let mut next_state = state
  while !pending_controls.is_empty() {
    match pending_controls.pop() {
      Some(ReleaseTerminal) =>
        next_state = release_terminal_state(program, next_state)
      Some(RestoreTerminal) =>
        next_state = restore_terminal_state(program, next_state, current_view)
      None => ()
    }
  }
  next_state
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] desired_view(
  program : Program[Model, Msg],
  model : Model,
) -> View {
  if program.rendering {
    match program.structured_view {
      Some(structured_view) => structured_view(model)
      None => View::text((program.view)(model))
    }
  } else {
    View::text("")
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] flush_view(
  program : Program[Model, Msg],
  rendered_view : String,
  next_view : View,
  prev_line_count : Int,
  applied_terminal_state : RuntimeViewTerminalState,
  terminal_active : Bool,
  last_render_ms : Int64,
  force : Bool,
  full_repaint : Bool,
  render_context? : @profile.RenderContext = applied_terminal_state.render_context,
) -> (String, Int, Int64, Int64?, RuntimeViewTerminalState) {
  let next_content = next_view.content
  if !program.rendering || !terminal_active {
    (
      rendered_view,
      prev_line_count,
      last_render_ms,
      None,
      applied_terminal_state,
    )
  } else {
    let (terminal_prefix, next_terminal_state) = view_terminal_state_delta(
      applied_terminal_state, next_view, render_context,
    )
    let terminal_changed = next_terminal_state != applied_terminal_state
    let content_changed = full_repaint || next_content != rendered_view
    if !content_changed && !terminal_changed {
      return (
        rendered_view,
        prev_line_count,
        last_render_ms,
        None,
        applied_terminal_state,
      )
    }
    let now = runtime_now_ms()
    let next_render_deadline = next_render_deadline_after(
      program, last_render_ms,
    )
    if force || terminal_changed || now >= next_render_deadline {
      let content_output = if content_changed {
        let ops = diff_views(rendered_view, next_content)
        if full_repaint || ops.is_empty() {
          render_full(next_content, prev_line_count)
        } else {
          render_patch(ops)
        }
      } else {
        ""
      }
      let terminal_suffix = view_cursor_position_suffix(
        applied_terminal_state,
        next_terminal_state,
        content_output != "",
      )
      let output = terminal_prefix + content_output + terminal_suffix
      let output = if program.synchronized_output && output != "" {
        begin_sync_update() + output + end_sync_update()
      } else {
        output
      }
      emit_output(program, output)
      (
        if content_changed {
          next_content
        } else {
          rendered_view
        },
        if content_changed {
          line_count(next_content)
        } else {
          prev_line_count
        },
        if content_changed {
          render_cadence_mark_at_or_before(program, now)
        } else {
          last_render_ms
        },
        None,
        next_terminal_state,
      )
    } else {
      (
        rendered_view,
        prev_line_count,
        last_render_ms,
        Some(next_render_deadline),
        applied_terminal_state,
      )
    }
  }
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] drain_runtime(
  program : Program[Model, Msg],
  model : Model,
  state : RuntimeRenderState,
  sent_msgs : @queue.Queue[Msg],
  pending : @queue.Queue[RuntimeEffect[Msg]],
  timers : Array[RuntimeTimer[Msg]],
  sequences : @deque.Deque[Array[Cmd[Msg]]],
) -> (Model, RuntimeExitReason?, Bool, RuntimeRenderState) {
  let mut current_model = model
  let mut current_state = state
  let mut reason : RuntimeExitReason? = None
  let mut updated = false
  while reason is None {
    let mut progressed = false

    while reason is None && !sent_msgs.is_empty() {
      let msg = sent_msgs.pop().unwrap()
      let (next_model, should_quit, cmd) = apply_msg(
        program, current_model, msg,
      )
      current_model = next_model
      updated = true
      progressed = true
      queue_cmd_effects(cmd, runtime_now_ms(), pending, timers, sequences)
      if should_quit {
        reason = Some(RuntimeExitQuit)
      }
    }

    while reason is None && !pending.is_empty() {
      let effect = pending.pop().unwrap()
      progressed = true
      match effect {
        RuntimeTask(task) => {
          match task() {
            Some(msg) => {
              let (next_model, should_quit, cmd) = apply_msg(
                program, current_model, msg,
              )
              current_model = next_model
              updated = true
              queue_cmd_effects(
                cmd,
                runtime_now_ms(),
                pending,
                timers,
                sequences,
              )
              if should_quit {
                reason = Some(RuntimeExitQuit)
              }
            }
            None => ()
          }
          current_state = apply_print_above_messages(
            program, current_state, current_model,
          )
        }
        RuntimeScreen(command) =>
          current_state = apply_screen_command(program, current_state, command)
        RuntimeTerminal(command) =>
          current_state = apply_terminal_command(
            program, current_state, command,
          )
        RuntimeLifecycle(msg) =>
          match msg {
            Quit => {
              let (next_model, _should_quit, cmd, lifecycle_updated) = apply_lifecycle_msg(
                program,
                current_model,
                Quit,
              )
              current_model = next_model
              if lifecycle_updated {
                updated = true
              }
              queue_cmd_effects(
                cmd,
                runtime_now_ms(),
                pending,
                timers,
                sequences,
              )
              reason = Some(RuntimeExitQuit)
            }
            Interrupt => {
              let (next_model, _should_quit, cmd, lifecycle_updated) = apply_lifecycle_msg(
                program,
                current_model,
                Interrupt,
              )
              current_model = next_model
              if lifecycle_updated {
                updated = true
              }
              queue_cmd_effects(
                cmd,
                runtime_now_ms(),
                pending,
                timers,
                sequences,
              )
              reason = Some(RuntimeExitInterrupted)
            }
            Suspend => {
              let (suspend_model, suspend_quit, suspend_cmd, suspend_updated) = apply_lifecycle_msg(
                program,
                current_model,
                Suspend,
              )
              current_model = suspend_model
              if suspend_updated {
                updated = true
              }
              queue_cmd_effects(
                suspend_cmd,
                runtime_now_ms(),
                pending,
                timers,
                sequences,
              )
              if suspend_quit {
                reason = Some(RuntimeExitQuit)
              } else {
                let current_view = desired_view(program, current_model)
                current_state = release_terminal_state(program, current_state)
                (program.suspend_runner)()
                current_state = restore_terminal_state(
                  program, current_state, current_view,
                )
                let (resume_model, resume_quit, resume_cmd, resume_updated) = apply_lifecycle_msg(
                  program,
                  current_model,
                  Resume,
                )
                current_model = resume_model
                if resume_updated {
                  updated = true
                }
                queue_cmd_effects(
                  resume_cmd,
                  runtime_now_ms(),
                  pending,
                  timers,
                  sequences,
                )
                if resume_quit {
                  reason = Some(RuntimeExitQuit)
                }
              }
            }
            Resume => {
              let (next_model, should_quit, cmd, lifecycle_updated) = apply_lifecycle_msg(
                program,
                current_model,
                Resume,
              )
              current_model = next_model
              if lifecycle_updated {
                updated = true
              }
              queue_cmd_effects(
                cmd,
                runtime_now_ms(),
                pending,
                timers,
                sequences,
              )
              if should_quit {
                reason = Some(RuntimeExitQuit)
              }
            }
          }
        RuntimeExec(process, to_msg) => {
          let current_view = desired_view(program, current_model)
          let was_active = current_state.terminal_active
          if was_active {
            current_state = release_terminal_state(program, current_state)
          }
          let exec_result = (program.exec_runner)(process)
          if was_active {
            current_state = restore_terminal_state(
              program, current_state, current_view,
            )
          }
          let (next_model, should_quit, cmd) = apply_msg(
            program,
            current_model,
            to_msg(exec_result),
          )
          current_model = next_model
          updated = true
          queue_cmd_effects(cmd, runtime_now_ms(), pending, timers, sequences)
          if should_quit {
            reason = Some(RuntimeExitExec(exec_result))
          }
        }
        RuntimeError(message) => reason = Some(RuntimeExitError(message))
      }
    }

    if reason is Some(_) {
      break
    }

    if pending.is_empty() && sent_msgs.is_empty() {
      match sequences.pop_front() {
        Some([head, .. tail]) => {
          queue_cmd_effects(head, runtime_now_ms(), pending, timers, sequences)
          if !tail.is_empty() {
            sequences.push_back(tail.to_owned())
          }
          progressed = true
        }
        Some([]) => progressed = true
        None => ()
      }
    }

    if !progressed {
      break
    }
  }
  (current_model, reason, updated, current_state)
}

///|
#cfg(any(target="native", target="llvm"))
fn[Model, Msg] run_internal(
  program : Program[Model, Msg],
  on_start : (ProgramHandle[Msg]) -> Unit,
) -> RunResult[Model] {
  pippa_init_wakeup()
  defer pippa_close_wakeup()

  let init_res = (program.init)()
  let mut model = init_res.model
  let sent_msgs = @queue.Queue([])
  let pending_controls = @queue.Queue([])
  let handle_closed = Ref(false)
  let handle = ProgramHandle::new(
    sent_msgs, pending_controls, pippa_signal_wakeup, handle_closed,
  )
  let pending = @queue.Queue([])
  let sequences = @deque.Deque([])
  let mut timers : Array[RuntimeTimer[Msg]] = []
  queue_cmd_effects(init_res.cmd, runtime_now_ms(), pending, timers, sequences)

  let terminal_active = program.rendering || program.read_stdin
  let mut render_state = initial_render_state(program, terminal_active)
  if render_state.terminal_active {
    activate_terminal(program)
  }
  if render_state.terminal_active && program.read_stdin {
    queue_startup_color_detection(pending)
  }

  let (initial_model, initial_reason, _initial_updated) = apply_initial_window_size(
    program, model, pending, timers, sequences,
  )
  model = initial_model
  let (boot_model, boot_reason, _boot_updated, boot_state) = drain_runtime(
    program, model, render_state, sent_msgs, pending, timers, sequences,
  )
  model = boot_model
  render_state = boot_state

  let mut desired = desired_view(program, model)
  render_state = drain_controls(
    program, pending_controls, render_state, desired,
  )
  let (
    initial_rendered,
    initial_line_count,
    initial_last_render_ms,
    initial_render_due_at,
    initial_view_terminal_state,
  ) = flush_view(
    program,
    render_state.rendered,
    desired,
    render_state.prev_line_count,
    render_state.view_terminal_state,
    render_state.terminal_active,
    render_state.last_render_ms,
    true,
    true,
    render_context=render_state.render_context,
  )
  render_state = {
    ..render_state,
    rendered: initial_rendered,
    prev_line_count: initial_line_count,
    last_render_ms: initial_last_render_ms,
    render_due_at: initial_render_due_at,
    force_full_repaint: false,
    view_terminal_state: initial_view_terminal_state,
  }
  on_start(handle)

  let mut stdin_buf : Array[Byte] = []
  let mut raw_buf : Array[Byte] = []
  let mut exit_reason = match initial_reason {
    Some(reason) => Some(reason)
    None => boot_reason
  }

  while exit_reason is None {
    render_state = drain_controls(
      program, pending_controls, render_state, desired,
    )
    if is_cancelled(program) {
      exit_reason = Some(RuntimeExitCompleted)
      break
    }

    let (drained_model, drained_reason, drained_updated, drained_render_state) = drain_runtime(
      program, model, render_state, sent_msgs, pending, timers, sequences,
    )
    model = drained_model
    render_state = drained_render_state
    match drained_reason {
      Some(reason) => exit_reason = Some(reason)
      None => ()
    }
    if drained_updated || render_state.force_full_repaint {
      desired = desired_view(program, model)
      let (
        next_rendered,
        next_line_count,
        next_last_render_ms,
        next_render_due_at,
        next_view_terminal_state,
      ) = flush_view(
        program,
        render_state.rendered,
        desired,
        render_state.prev_line_count,
        render_state.view_terminal_state,
        render_state.terminal_active,
        render_state.last_render_ms,
        exit_reason is Some(_) || render_state.force_full_repaint,
        render_state.force_full_repaint,
        render_context=render_state.render_context,
      )
      render_state = {
        ..render_state,
        rendered: next_rendered,
        prev_line_count: next_line_count,
        last_render_ms: next_last_render_ms,
        render_due_at: next_render_due_at,
        force_full_repaint: false,
        view_terminal_state: next_view_terminal_state,
      }
    }
    if exit_reason is Some(_) {
      break
    }

    let mut updated = false
    let polled_input_events = poll_input_events(program)
    let (input_context_state, input_context_updated) = runtime_apply_render_context_events(
      render_state, polled_input_events,
    )
    render_state = input_context_state
    if input_context_updated {
      updated = true
    }
    let (input_model, input_reason, input_updated) = apply_input_events(
      program, model, polled_input_events, pending, timers, sequences,
    )
    model = input_model
    match input_reason {
      Some(reason) => exit_reason = Some(reason)
      None => ()
    }
    if input_updated {
      updated = true
    }
    let (raw_events, next_raw_buf) = poll_raw_input_events(program, raw_buf)
    raw_buf = next_raw_buf
    let (raw_context_state, raw_context_updated) = runtime_apply_render_context_events(
      render_state, raw_events,
    )
    render_state = raw_context_state
    if raw_context_updated {
      updated = true
    }
    let (raw_model, raw_reason, raw_updated) = apply_input_events(
      program, model, raw_events, pending, timers, sequences,
    )
    model = raw_model
    match raw_reason {
      Some(reason) => exit_reason = Some(reason)
      None => ()
    }
    if raw_updated {
      updated = true
    }
    if exit_reason is Some(_) {
      break
    }

    if !updated {
      let poll_result = pippa_poll_events(
        runtime_timeout_ms(program, timers, render_state.render_due_at),
        if render_state.terminal_active && program.read_stdin {
          1
        } else {
          0
        },
      )
      if poll_result < 0 {
        exit_reason = Some(RuntimeExitError("poll failed"))
        break
      }

      if poll_result == 0 {
        let now = runtime_now_ms()
        timers = queue_due_timers(program, now, timers, pending)
      } else {
        if (poll_result & 4) != 0 {
          pippa_drain_wakeup()
        }
        if (poll_result & 1) != 0 &&
          render_state.terminal_active &&
          program.read_stdin {
          let b = pippa_read_byte()
          if b < 0 {
            exit_reason = Some(RuntimeExitError("failed to read stdin"))
            break
          }

          stdin_buf.push(b.to_byte())

          // Drain any immediately available bytes so we parse the whole burst
          // in one pass rather than re-scanning a growing buffer per byte.
          while (pippa_poll_events(0, 1) & 1) != 0 {
            let nb = pippa_read_byte()
            if nb < 0 {
              break
            }
            stdin_buf.push(nb.to_byte())
          }

          let bytes = Bytes::from_array(stdin_buf)
          let bv = bytes[:]
          let (events, leftover) = parse_all(bv)

          stdin_buf = leftover.to_array()
          let (stdin_context_state, stdin_context_updated) = runtime_apply_render_context_events(
            render_state, events,
          )
          render_state = stdin_context_state
          if stdin_context_updated {
            updated = true
          }

          let (next_model, input_reason, input_updated) = apply_input_events(
            program, model, events, pending, timers, sequences,
          )
          model = next_model
          match input_reason {
            Some(reason) => exit_reason = Some(reason)
            None => ()
          }
          if input_updated {
            updated = true
          }
        }
      }
    }

    if render_state.terminal_active &&
      program.read_stdin &&
      pippa_check_resize() != 0 {
      let size = current_window_size()
      render_state = runtime_render_state_with_window_size(render_state, size)
      updated = true
      let (resize_model, resize_reason, _resize_updated) = apply_window_size(
        program, model, size, pending, timers, sequences,
      )
      model = resize_model
      match resize_reason {
        Some(reason) => exit_reason = Some(reason)
        None => ()
      }
    }

    let (final_model, final_reason, final_updated, final_render_state) = drain_runtime(
      program, model, render_state, sent_msgs, pending, timers, sequences,
    )
    model = final_model
    render_state = final_render_state
    match final_reason {
      Some(reason) => exit_reason = Some(reason)
      None => ()
    }
    if final_updated {
      updated = true
    }

    if updated {
      desired = desired_view(program, model)
    }
    let should_flush_view = updated ||
      render_state.force_full_repaint ||
      (match render_state.render_due_at {
        Some(deadline) =>
          render_state.terminal_active && runtime_now_ms() >= deadline
        None => false
      })
    if should_flush_view {
      let (
        next_rendered,
        next_line_count,
        next_last_render_ms,
        next_render_due_at,
        next_view_terminal_state,
      ) = flush_view(
        program,
        render_state.rendered,
        desired,
        render_state.prev_line_count,
        render_state.view_terminal_state,
        render_state.terminal_active,
        render_state.last_render_ms,
        exit_reason is Some(_) || render_state.force_full_repaint,
        render_state.force_full_repaint,
        render_context=render_state.render_context,
      )
      render_state = {
        ..render_state,
        rendered: next_rendered,
        prev_line_count: next_line_count,
        last_render_ms: next_last_render_ms,
        render_due_at: next_render_due_at,
        force_full_repaint: false,
        view_terminal_state: next_view_terminal_state,
      }
    }
  }
  handle_closed.val = true

  if render_state.terminal_active {
    if render_state.alt_screen == program.alt_screen &&
      render_state.cursor_hidden == program.hide_cursor &&
      render_state.focus_reporting ==
      (program.read_stdin && program.focus_reporting) &&
      render_state.bracketed_paste ==
      (program.read_stdin && program.bracketed_paste) {
      deactivate_terminal(program)
    } else {
      deactivate_terminal_with_modes(
        program,
        render_state.alt_screen,
        render_state.cursor_hidden,
        render_state.focus_reporting,
        render_state.bracketed_paste,
      )
    }
  }
  match exit_reason {
    Some(reason) => run_result_from_reason(model, reason)
    None => RunCompleted(model)
  }
}