///|
/// Sends input to the child's terminal.
/// These are the standard `@async/io.Writer` methods with their usual
/// semantics: `write_once` performs a single write and reports how many
/// bytes were accepted, `write` writes the whole buffer before returning,
/// and `write_reader` forwards everything read from another reader.
pub extend Pty with @async/io.Writer::{write_once, write, write_reader}

///|
/// Reads the child's terminal output (stdout and stderr merged).
/// These are the standard `@async/io.Reader` methods with their usual
/// semantics: `read` reads what is immediately available, `read_some`
/// returns as soon as some data arrives, `read_all` collects until EOF, and
/// `read_until` / `read_exactly` / `drop` behave as documented on
/// `@async/io.Reader`.
///
/// Read concurrently with `Pty::wait` — do not wait for the child and only
/// then read: the pty master is a bounded kernel queue, and on macOS the
/// kernel discards whatever is still queued a few hundred milliseconds after
/// the child exits. Reading late yields a clean but empty EOF.
///
/// On Windows the output is a ConPTY screen rendering rather than a raw
/// byte stream: expect VT escape sequences (including an initial
/// clear-screen) surrounding the child's output.
pub extend Pty with @async/io.Reader::{
  read,
  read_some,
  drop,
  read_all,
  read_until,
  read_exactly,
}

///|
#deprecated("internal")
#doc(hidden)
pub extend Pty with @async/io.Reader::{_direct_read, _get_internal_buffer}

///|
// Wait for `pid` while `background` runs alongside (close the terminal, grace
// period, hard kill, ...). The waiter is a spawned task on purpose, never the
// main task of the group: if the runtime cancels the waiter directly
// (`EventLoop::cleanup` after a fatal error in the host event loop), the
// group fails with `@async.WaitedTaskAlreadyCancelled` instead of reaching
// `with_task_group`'s `result.unwrap()` on a `Done` group with no result,
// which aborts the whole process (moonbitlang/async task_group.mbt:270 as of
// 0.22.4). See repro/ for both shapes.
// The waiter is spawned first so that it registers for the child before
// `background` starts tearing the terminal down.
//
// `on_waiter` is a test hook (pty_wbtest.mbt): it hands out the waiter task
// so a test can cancel it the way the runtime would. Only that whitebox test
// supplies it, so silence unused_optional_argument.
#warnings("-unused_optional_argument")
async fn wait_pid_with(
  pid : Int,
  background : async () -> Unit,
  on_waiter? : (@async.Task[Int]) -> Unit,
) -> Int {
  @async.with_task_group() <| g => {
    let waiter = g.spawn(() => @async/process.wait_pid(pid))
    if on_waiter is Some(hook) {
      hook(waiter)
    }
    g.spawn_bg(no_wait=true, background)
    waiter.wait()
  }
}