///|
fn empty_limits() -> ResourceLimits {
  {
    cpu_seconds: None,
    address_space_bytes: None,
    file_size_bytes: None,
    open_files: None,
    processes: None,
  }
}

///|
/// Start a deny-by-default policy. Unknown syscalls fail with EPERM.
pub fn Policy::deny_by_default(name? : String = "custom") -> Policy {
  {
    name,
    default_action: Errno(1),
    syscall_rules: [],
    path_rules: [],
    limits: empty_limits(),
    require_landlock: true,
  }
}

///|
/// Start a permissive policy for audit and migration work.
pub fn Policy::allow_by_default(name? : String = "audit") -> Policy {
  {
    name,
    default_action: Allow,
    syscall_rules: [],
    path_rules: [],
    limits: empty_limits(),
    require_landlock: false,
  }
}

///|
/// Add or override no rules: duplicate syscall names are rejected at compile
/// time so the resulting policy is unambiguous.
pub fn Policy::on_syscall(
  self : Policy,
  name : String,
  action : SeccompAction,
) -> Policy {
  self.syscall_rules.push({ name, action, })
  self
}

///|
pub fn Policy::allow_syscall(self : Policy, name : String) -> Policy {
  self.on_syscall(name, Allow)
}

///|
pub fn Policy::deny_syscall(
  self : Policy,
  name : String,
  errno? : Int = 1,
) -> Policy {
  self.on_syscall(name, Errno(errno))
}

///|
pub fn Policy::allow_path(
  self : Policy,
  path : String,
  rights : Array[AccessRight],
) -> Policy {
  self.path_rules.push({ path, rights, })
  self
}

///|
pub fn Policy::allow_read(self : Policy, path : String) -> Policy {
  self.allow_path(path, [ReadFile, ReadDir])
}

///|
pub fn Policy::allow_write(self : Policy, path : String) -> Policy {
  self.allow_path(path, [ReadFile, ReadDir, WriteFile, Remove, MakeNode, Refer])
}

///|
pub fn Policy::with_landlock_required(self : Policy, required : Bool) -> Policy {
  {
    name: self.name,
    default_action: self.default_action,
    syscall_rules: self.syscall_rules,
    path_rules: self.path_rules,
    limits: self.limits,
    require_landlock: required,
  }
}

///|
pub fn Policy::limit_cpu(self : Policy, seconds : Int) -> Policy {
  let limits = { ..self.limits, cpu_seconds: Some(seconds), }
  { ..self, limits, }
}

///|
pub fn Policy::limit_memory(self : Policy, bytes : Int64) -> Policy {
  let limits = { ..self.limits, address_space_bytes: Some(bytes), }
  { ..self, limits, }
}

///|
pub fn Policy::limit_file_size(self : Policy, bytes : Int64) -> Policy {
  let limits = { ..self.limits, file_size_bytes: Some(bytes), }
  { ..self, limits, }
}

///|
pub fn Policy::limit_open_files(self : Policy, count : Int) -> Policy {
  let limits = { ..self.limits, open_files: Some(count), }
  { ..self, limits, }
}

///|
pub fn Policy::limit_processes(self : Policy, count : Int) -> Policy {
  let limits = { ..self.limits, processes: Some(count), }
  { ..self, limits, }
}

///|
fn allow_many(policy : Policy, names : Array[String]) -> Policy {
  for name in names {
    ignore(policy.allow_syscall(name))
  }
  policy
}

///|
/// Create one of the maintained baseline profiles.
pub fn Policy::from_profile(profile : Profile) -> Policy {
  match profile {
    Minimal => {
      let policy = Policy::deny_by_default(name="minimal")
      allow_many(policy, ["exit", "exit_group", "rt_sigreturn"])
    }
    ConsoleTool => {
      let policy = Policy::deny_by_default(name="console-tool")
      allow_many(policy, [
        "read", "write", "close", "fstat", "newfstatat", "lseek", "mmap", "mprotect",
        "munmap", "brk", "rt_sigaction", "rt_sigprocmask", "rt_sigreturn", "ioctl",
        "fcntl", "pread64", "access", "openat", "readlink", "arch_prctl", "set_tid_address",
        "set_robust_list", "prlimit64", "getrandom", "rseq", "futex", "clock_gettime",
        "statx", "splice", "sigaltstack", "statfs", "poll", "prctl", "sched_getaffinity",
        "getpid", "gettid", "execve", "exit", "exit_group",
      ])
    }
    BuildStep => {
      let base = Policy::from_profile(ConsoleTool)
      allow_many({ ..base, name: "build-step", }, [
        "clone", "clone3", "wait4", "pipe2", "dup", "dup2", "dup3", "getcwd", "chdir",
        "mkdirat", "unlinkat", "renameat2",
      ])
    }
  }
}