///| Running the command attached to a matched rule.

///|
fn trim_output(text : String) -> String {
  trim(text)
}

///|
pub struct ActionOutcome {
  exit_code : Int32
  timed_out : Bool
}

///|
/// Execute one matched rule, logging what ran and how it ended.
pub async fn run_action(
  account : String,
  mailbox : String,
  message : MailMessage,
  hit : RuleMatch,
) -> ActionOutcome {
  let context : ActionContext = {
    account,
    mailbox,
    message,
    rule: hit.rule,
    keywords: hit.keywords,
  }
  let env = action_env(context)
  let timeout = hit.rule.timeout_ms
  log_info(
    "rule '" +
    hit.rule.name +
    "' matched uid " +
    message.uid +
    " in " +
    mailbox +
    " (" +
    hit.keywords.join(", ") +
    ")",
  )
  let (program, args) = if hit.rule.argv.length() > 0 {
    let rendered : Array[String] = []
    for arg in hit.rule.argv {
      rendered.push(render_template(arg, context))
    }
    let program = rendered[0]
    let rest : Array[String] = []
    let mut index = 1
    while index < rendered.length() {
      rest.push(rendered[index])
      index = index + 1
    }
    (program, rest)
  } else {
    ("sh", ["-c", render_template(hit.rule.shell_command, context)])
  }
  let cwd : StringView? = if hit.rule.workdir == "" {
    None
  } else {
    Some(hit.rule.workdir[:])
  }
  let collect = () => {
    @process.collect_output(
      program,
      args,
      extra_env=env,
      inherit_env=true,
      cwd?,
    )
  }
  let result = @async.with_timeout_opt(timeout, () => collect()) catch {
    err => {
      log_warn(
        "rule '" + hit.rule.name + "' failed to start: " + err.to_string(),
      )
      return { exit_code: -1, timed_out: false, }
    }
  }
  match result {
    None => {
      log_warn(
        "rule '" +
        hit.rule.name +
        "' timed out after " +
        format_duration(timeout),
      )
      { exit_code: -1, timed_out: true, }
    }
    Some((exit_code, stdout, stderr)) => {
      let out = trim_output(stdout.text() catch { _ => "" })
      let err = trim_output(stderr.text() catch { _ => "" })
      if out != "" {
        log_debug("rule '" + hit.rule.name + "' stdout: " + out)
      }
      if err != "" {
        log_debug("rule '" + hit.rule.name + "' stderr: " + err)
      }
      if exit_code != 0 {
        let detail = if err != "" {
          ": " + truncate_chars(err, 500)
        } else if out != "" {
          ": " + truncate_chars(out, 500)
        } else {
          ""
        }
        log_warn(
          "rule '" +
          hit.rule.name +
          "' exited with status " +
          exit_code.to_string() +
          detail,
        )
      }
      { exit_code, timed_out: false, }
    }
  }
}