// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// A handle to a spawned orphan process. Must be explicitly closed.
pub struct OrphanProcess {
  pid : Int32
  /// Redirected `stdin` channel of the process.
  /// This field is only meaningful if the process is spawned
  /// with `stdin=FeedFromParent`.
  /// In this case, it can be used to write to stdin of the child process.
  stdin : WriteToProcess
  /// Redirected `stdout` channel of the process.
  /// This field is only meaningful if the process is spawned
  /// with `stdout=Collect`.
  /// In this case, it can be used to read from the stdout of the child process.
  stdout : ReadFromProcess
  /// Redirected `stderr` channel of the process.
  /// This field is only meaningful if the process is spawned
  /// with `stderr=Collect`.
  /// In this case, it can be used to read from the stderr of the child process.
  stderr : ReadFromProcess
  priv handle : @event_loop.Process
}

///|
/// Close handle to an orphan process, release associated resource.
/// Note that closing the handle will not terminate the child process.
pub fn OrphanProcess::close(self : OrphanProcess) -> Unit {
  self.handle.close()
}

///|
/// Execute a system process with command `cmd`,
/// and provide `args` as extra arguments to `cmd`.
/// Both `cmd` and elements of `args` are encoded using UTF8.
/// A handle similar to `Process` is returned, see `@process.spawn` for more details.
///
/// The spawned process would be *orphan*, meaning that it will
/// keep running until completion or explicitly terminated,
/// even if the task that calls `spawn_orphan` is cancelled.
/// Users are recommended to use `@process.run` whenever possible,
/// because `@process.spawn`/`@process.run` has better structured concurrency integration.
/// `spawn_orphan` should only be used when the process is intended to be orphan
/// (i.e. keep running after parent process terminates).
///
/// The meaning of the arguments is the same as `@process.spawn`,
/// see `@process.spawn` for more details.
pub async fn spawn_orphan(
  cmd : StringView,
  args : ArrayView[String],
  extra_env~ : Map[String, String] = Map([]),
  inherit_env~ : Bool = true,
  stdin~ : ProcessInput = Inherit,
  stdout~ : ProcessOutput = Inherit,
  stderr~ : ProcessOutput = Inherit,
  cwd? : StringView,
  no_console_window~ : Bool = false,
) -> OrphanProcess {
  let (proc, stdin, stdout, stderr) = raw_spawn(
    cmd,
    args,
    extra_env~,
    inherit_env~,
    stdin~,
    stdout~,
    stderr~,
    cwd~,
    no_console_window~,
    is_orphan=true,
    context="@process.spawn_orphan()",
  )
  { pid: proc.pid, stdin, stdout, stderr, handle: proc, }
}

///|
/// Wait for the completion of an orphan process and return its exit code.
/// If the process is killed by signal, the result would be `-signal_number`.
pub async fn OrphanProcess::wait(self : OrphanProcess) -> Int32 {
  self.handle.wait_pid(context="@process.OrphanProcess::wait()")
}

///|
/// Wait for the process with specfic ID to terminate,
/// and return the exit code of the process.
/// If the process is killed by a signal, the result would be `-signal_number`.
pub async fn wait_pid(pid : Int32) -> Int32 {
  let context = "@process.wait_pid()"
  let process = @event_loop.Process::from_pid(pid, context~)
  defer process.close()
  process.wait_pid(context~)
}