///|
priv struct ActionDocument {
  kind : String
  errno : Int?
} derive(FromJson, ToJson)

///|
priv struct SyscallDocument {
  name : String
  action : ActionDocument
} derive(FromJson, ToJson)

///|
priv struct PathDocument {
  path : String
  rights : Array[String]
} derive(FromJson, ToJson)

///|
priv struct LimitsDocument {
  cpu_seconds : Int?
  address_space_bytes : Int64?
  file_size_bytes : Int64?
  open_files : Int?
  processes : Int?
} derive(FromJson, ToJson)

///|
priv struct PolicyDocument {
  version : Int
  name : String
  default_action : ActionDocument
  syscall_rules : Array[SyscallDocument]
  path_rules : Array[PathDocument]
  limits : LimitsDocument
  require_landlock : Bool
} derive(FromJson, ToJson)

///|
pub(all) suberror ProfileError {
  InvalidJson(String)
  UnsupportedVersion(Int)
  InvalidAction(String)
  InvalidRight(String)
} derive(Debug)

///|
pub extend ProfileError with @moonbitlang/core/debug.Debug::{to_repr}

///|
fn parse_action(document : ActionDocument) -> SeccompAction raise ProfileError {
  match document.kind {
    "allow" => Allow
    "errno" =>
      match document.errno {
        Some(errno) => Errno(errno)
        None => raise InvalidAction("errno action requires an errno value")
      }
    "trap" => Trap
    "log" => Log
    "kill_process" => KillProcess
    other => raise InvalidAction(other)
  }
}

///|
fn write_action(action : SeccompAction) -> ActionDocument {
  match action {
    Allow => { kind: "allow", errno: None, }
    Errno(errno) => { kind: "errno", errno: Some(errno), }
    Trap => { kind: "trap", errno: None, }
    Log => { kind: "log", errno: None, }
    KillProcess => { kind: "kill_process", errno: None, }
  }
}

///|
fn parse_right(name : String) -> AccessRight raise ProfileError {
  match name {
    "read_file" => ReadFile
    "read_dir" => ReadDir
    "write_file" => WriteFile
    "remove" => Remove
    "make_node" => MakeNode
    "execute" => Execute
    "refer" => Refer
    _ => raise InvalidRight(name)
  }
}

///|
fn write_right(right : AccessRight) -> String {
  match right {
    ReadFile => "read_file"
    ReadDir => "read_dir"
    WriteFile => "write_file"
    Remove => "remove"
    MakeNode => "make_node"
    Execute => "execute"
    Refer => "refer"
  }
}

///|
/// Parse the version 1 JSON policy schema. The result is still validated and
/// compiled before it can be used by the runtime.
pub fn Policy::from_json(text : String) -> Policy raise ProfileError {
  let json = @json.parse(text) catch {
    error => raise InvalidJson(error.to_string())
  }
  let document : PolicyDocument = @json.from_json(json) catch {
    error => raise InvalidJson(error.to_string())
  }
  if document.version != 1 {
    raise UnsupportedVersion(document.version)
  }
  let syscall_rules : Array[SyscallRule] = []
  for rule in document.syscall_rules {
    syscall_rules.push({ name: rule.name, action: parse_action(rule.action), })
  }
  let path_rules : Array[PathRule] = []
  for rule in document.path_rules {
    let rights : Array[AccessRight] = []
    for right in rule.rights {
      rights.push(parse_right(right))
    }
    path_rules.push({ path: rule.path, rights, })
  }
  {
    name: document.name,
    default_action: parse_action(document.default_action),
    syscall_rules,
    path_rules,
    limits: {
      cpu_seconds: document.limits.cpu_seconds,
      address_space_bytes: document.limits.address_space_bytes,
      file_size_bytes: document.limits.file_size_bytes,
      open_files: document.limits.open_files,
      processes: document.limits.processes,
    },
    require_landlock: document.require_landlock,
  }
}

///|
/// Serialize a policy using the stable version 1 JSON document schema.
pub fn Policy::to_json(self : Policy) -> String {
  let syscall_rules : Array[SyscallDocument] = []
  for rule in self.syscall_rules {
    syscall_rules.push({ name: rule.name, action: write_action(rule.action), })
  }
  let path_rules : Array[PathDocument] = []
  for rule in self.path_rules {
    let rights : Array[String] = []
    for right in rule.rights {
      rights.push(write_right(right))
    }
    path_rules.push({ path: rule.path, rights, })
  }
  let document : PolicyDocument = {
    version: 1,
    name: self.name,
    default_action: write_action(self.default_action),
    syscall_rules,
    path_rules,
    limits: {
      cpu_seconds: self.limits.cpu_seconds,
      address_space_bytes: self.limits.address_space_bytes,
      file_size_bytes: self.limits.file_size_bytes,
      open_files: self.limits.open_files,
      processes: self.limits.processes,
    },
    require_landlock: self.require_landlock,
  }
  @json.to_json(document).stringify(indent=2)
}