import {
  _args,
  _cwd,
  _env,
  _setEnv,
  _exit,
  _openUrl,
  _screenshot,
  _isTTY,
  _readStdin,
  _setTitle,
  _urlHost,
 } from "agency-lang/stdlib-lib/system.js"
import { _notify } from "agency-lang/stdlib-lib/notify.js"
import { _realTarget } from "agency-lang/stdlib-lib/fs.js"

/** @module Talk to the host OS: read command-line args and environment
variables, find the current directory, show notifications, take screenshots,
open URLs, and read from stdin.

  ```ts
  import { args, env, notify } from "std::system"

  node main() {
    const home = env("HOME") with approve
    notify("Done", "Processed ${args().length} arguments")
  }
  ```
*/

effect std::screenshot { filepath: string }
effect std::exit { code: number }
@always(name)
effect std::env { name: string }
@always(name)
effect std::setEnv { name: string, value: string }
@always(host)
effect std::openUrl { url: string, host: string }
effect std::notify { title: string, body: string }

@capabilities(subprocess)
export def notify(title: string, message: string): boolean raises <std::notify> {
  """
  Show a native OS notification. Returns true if it was sent.
  """
  return interrupt std::notify("Are you sure you want to show this notification?", {
    title: title,
    body: message
  })
  return _notify(title, message)
}

/** Ctrl-C, a race loss, or a time-guard abort interrupts an in-progress
screencapture. */
@capabilities(fileRead, subprocess)
export def screenshot(
  filepath: string,
  x: number = -1,
  y: number = -1,
  width: number = -1,
  height: number = -1,
  allowedPaths: string[] = [],
) raises <std::screenshot> {
  """
  Take a screenshot and save it to a file. Pass x, y, width, and height to capture a specific region.

  @param filepath - Path to save the screenshot
  @param x - X coordinate of the capture region; -1 for the full screen
  @param y - Y coordinate of the capture region; -1 for the full screen
  @param width - Width of the capture region; -1 for the full screen
  @param height - Height of the capture region; -1 for the full screen
  @param allowedPaths - Only allow saving under these path prefixes
  """
  const real = try _realTarget(filepath)?
  return interrupt std::screenshot("Are you sure you want to take a screenshot? This will capture the screen contents, which may include sensitive information.", {
    filepath: real
  })

  _screenshot(real, x, y, width, height, allowedPaths)
}

/**
Flushes pending Statelog deliveries before terminating. Other pending work
and cleanup may not finish.
*/
export def exit(code: number = 0) raises <std::exit> {
  """
  Terminate the process with an exit code.

  @param code - Zero for success, nonzero for failure.
  """
  return interrupt std::exit("Are you sure you want to exit the process?", {
    code: code
  })
  _exit(code)
}

export def args(): string[] {
  """
  Return the command-line arguments passed to the Agency program (excluding the node executable and script path).
  """
  return _args()
}

export def cwd(): string {
  """
  Return the run's current working directory as an absolute path.
  """
  return _cwd()
}

// A denied interrupt returns a failure that env converts to null.
@capabilities(env)
def requestEnvRead(name: string): string | null {
  return interrupt std::env("Are you sure you want to read this environment variable?", {
    name: name
  })
  return _env(name)
}

export idempotent def env(name: string): string | null {
  """
  Read an environment variable, or return null if unset or access is denied.
  """
  const value = requestEnvRead(name)
  if (isFailure(value)) {
    // A read the program is not allowed to make looks like a variable that
    // is not set. Callers already handle the unset case, so a denial cannot
    // be mistaken for a configured value.
    return null
  }
  return value
}

/**
On the Node host, the change affects this process and child processes
started afterward. It does not change the launching shell's environment.
*/
@capabilities(env)
export def setEnv(name: string, value: string): Result raises <std::setEnv> {
  """
  Set an environment variable on the host.

  @param name - A nonempty variable name without = or NUL bytes
  @param value - Must not contain NUL bytes
  """
  return interrupt std::setEnv("Are you sure you want to set this environment variable?", {
    name: name,
    value: value
  })
  return try _setEnv(name, value)
}

/**
Use this to distinguish interactive input from piped or redirected input.
*/
@capabilities(terminal)
export def isTTY(): boolean {
  """
  Return whether standard input is connected to a terminal.
  """
  return _isTTY()
}

@capabilities(terminal)
export def readStdin(): string {
  """
  Read piped or redirected standard input as text, waiting until the stream closes.
  """
  return _readStdin()
}

// The policy scope for std::openUrl: the hostname, so "approve always
// here" covers one site rather than one exact URL.
def urlHost(url: string): string {
  return _urlHost(url)
}

/** macOS-only. Ctrl-C, a race loss, or a time-guard abort kills the in-flight
`open` subprocess. */
@capabilities(subprocess)
export def openUrl(url: string): Result raises <std::openUrl> {
  """
  Open a URL in the user's default browser.
  """
  return interrupt std::openUrl("Are you sure you want to open this URL in the browser?", {
    url: url,
    host: urlHost(url)
  })

  return try _openUrl(url)
}

/**
Use a descriptive title to distinguish several running agents in system
monitors and `ps` output.
*/
export def setTitle(title: string): void {
  """
  Set the process title shown in system monitors.
  """
  return _setTitle(title)
}
