//
// Syd: rock-solid application kernel
// src/api.rs: JSON serializers for syd(2) API
//
// Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{os::fd::RawFd, sync::LazyLock};

use serde::ser::{Serialize, SerializeMap, Serializer};

use crate::{
    hash::SydHashMap,
    path::PATH_MAX,
    sandbox::{Version, ACTION_STR, SANDBOX_CAPS},
};

/// LazyLock instance of the syd(2) API spec.
pub static API_SPEC: LazyLock<Api> = LazyLock::new(|| Api {
    root: "/dev/syd".into(),
    version: crate::config::API_VERSION,
    methods: vec![
        Method {
            name: "check".into(),
            desc: "Check if syd(2) API is available".into(),
            ..Default::default()
        },
        Method {
            name: "panic".into(),
            desc: "Exit immediately with code 127".into(),
            path: Some("panic".into()),
            ..Default::default()
        },
        Method {
            name: "reset".into(),
            desc: "Reset sandboxing to the default state".into(),
            path: Some("reset".into()),
            ..Default::default()
        },
        Method {
            name: "ghost".into(),
            desc: "Initiate Ghost mode".into(),
            path: Some("ghost".into()),
            ..Default::default()
        },
        Method {
            name: "load".into(),
            desc: "Read configuration from the given file descriptor or builtin profile".into(),
            path: Some("load".into()),
            argv: Some(vec![Arg::FileDes, Arg::Profile]),
            argc: Some(vec![1]),
            ..Default::default()
        },
        Method {
            name: "lock".into(),
            desc: "Set the state of the sandbox lock".into(),
            path: Some("lock".into()),
            argv: Some(vec![Arg::State]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "enabled".into(),
            desc: "Check if sandboxing is enabled".into(),
            path: Some("sandbox".into()),
            base: Some(vec![Arg::Capability]),
            op: Some(Operator::Query),
            ..Default::default()
        },
        Method {
            name: "enable".into(),
            desc: "Enable or disable sandboxing".into(),
            path: Some("sandbox".into()),
            base: Some(vec![Arg::Capability]),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "default".into(),
            desc: "Set default action for sandboxing".into(),
            path: Some("default".into()),
            base: Some(vec![Arg::Capability]),
            argv: Some(vec![Arg::Action]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "append_add".into(),
            desc: "Add a pattern for append-only sandboxing".into(),
            path: Some("append".into()),
            argv: Some(vec![Arg::Glob]),
            argc: Some(vec![1]),
            op: Some(Operator::Append),
            ..Default::default()
        },
        Method {
            name: "append_remove".into(),
            desc: "Remove a pattern for append-only sandboxing".into(),
            path: Some("append".into()),
            argv: Some(vec![Arg::Glob]),
            argc: Some(vec![1]),
            op: Some(Operator::Remove),
            ..Default::default()
        },
        Method {
            name: "append_clear".into(),
            desc: "Remove all matching patterns for append-only sandboxing".into(),
            path: Some("append".into()),
            argv: Some(vec![Arg::Glob]),
            argc: Some(vec![1]),
            op: Some(Operator::Clear),
            ..Default::default()
        },
        Method {
            name: "block_add".into(),
            desc: "Add an IP network to the blocklist".into(),
            path: Some("block".into()),
            argv: Some(vec![Arg::Cidr]),
            argc: Some(vec![1]),
            op: Some(Operator::Append),
            ..Default::default()
        },
        Method {
            name: "block_remove".into(),
            desc: "Remove an IP network from the blocklist".into(),
            path: Some("block".into()),
            argv: Some(vec![Arg::Cidr]),
            argc: Some(vec![1]),
            op: Some(Operator::Remove),
            ..Default::default()
        },
        Method {
            name: "block_clear".into(),
            desc: "Clear the blocklist".into(),
            path: Some("block".into()),
            op: Some(Operator::Clear),
            ..Default::default()
        },
        Method {
            name: "block_merge".into(),
            desc: "Aggregate the blocklist".into(),
            path: Some("block".into()),
            op: Some(Operator::Exec),
            ..Default::default()
        },
        Method {
            name: "crypt_add".into(),
            desc: "Add a pattern for Crypt sandboxing".into(),
            path: Some("crypt".into()),
            argv: Some(vec![Arg::Glob]),
            argc: Some(vec![1]),
            op: Some(Operator::Append),
            ..Default::default()
        },
        Method {
            name: "crypt_remove".into(),
            desc: "Remove a pattern for Crypt sandboxing".into(),
            path: Some("crypt".into()),
            argv: Some(vec![Arg::Glob]),
            argc: Some(vec![1]),
            op: Some(Operator::Remove),
            ..Default::default()
        },
        Method {
            name: "crypt_clear".into(),
            desc: "Remove all matching patterns for Crypt sandboxing".into(),
            path: Some("crypt".into()),
            argv: Some(vec![Arg::Glob]),
            argc: Some(vec![1]),
            op: Some(Operator::Clear),
            ..Default::default()
        },
        Method {
            name: "force_add".into(),
            desc: "Add an integrity rule for Force sandboxing".into(),
            path: Some("force".into()),
            argv: Some(vec![Arg::Path, Arg::Hash, Arg::Action]),
            argc: Some(vec![2, 3]),
            op: Some(Operator::Append),
            exc: Some(
                [("$action".into(), vec!["allow".into()])]
                    .into_iter()
                    .collect::<SydHashMap<_, _>>(),
            ),
            ..Default::default()
        },
        Method {
            name: "force_remove".into(),
            desc: "Remove an integrity rule for Force sandboxing".into(),
            path: Some("force".into()),
            argv: Some(vec![Arg::Path]),
            argc: Some(vec![1]),
            op: Some(Operator::Remove),
            ..Default::default()
        },
        Method {
            name: "force_clear".into(),
            desc: "Remove all integrity rules for Force sandboxing".into(),
            path: Some("force".into()),
            op: Some(Operator::Clear),
            ..Default::default()
        },
        Method {
            name: "ioctl_add".into(),
            desc: "Add a request to the ioctl allowlist/denylist".into(),
            path: Some("ioctl".into()),
            base: Some(vec![Arg::Action]),
            argv: Some(vec![Arg::U64]),
            argc: Some(vec![1]),
            op: Some(Operator::Append),
            exc: Some(
                [(
                    "$action".into(),
                    vec![
                        "abort".into(),
                        "exit".into(),
                        "filter".into(),
                        "kill".into(),
                        "panic".into(),
                        "stop".into(),
                        "warn".into(),
                    ],
                )]
                .into_iter()
                .collect::<SydHashMap<_, _>>(),
            ),
            ..Default::default()
        },
        Method {
            name: "ioctl_remove".into(),
            desc: "Remove a request from the ioctl allowlist/denylist".into(),
            path: Some("ioctl".into()),
            base: Some(vec![Arg::Action]),
            argv: Some(vec![Arg::U64]),
            argc: Some(vec![1]),
            op: Some(Operator::Remove),
            exc: Some(
                [(
                    "$action".into(),
                    vec![
                        "abort".into(),
                        "exit".into(),
                        "filter".into(),
                        "kill".into(),
                        "panic".into(),
                        "stop".into(),
                        "warn".into(),
                    ],
                )]
                .into_iter()
                .collect::<SydHashMap<_, _>>(),
            ),
            ..Default::default()
        },
        Method {
            name: "rule_add".into(),
            desc: "Add an access control rule for sandboxing".into(),
            base: Some(vec![Arg::Action, Arg::Capability]),
            argv: Some(vec![Arg::Rule]),
            argc: Some(vec![1]),
            op: Some(Operator::Append),
            exc: Some(
                [(
                    "$caps".into(),
                    vec![
                        "crypt".into(),
                        "force".into(),
                        "ioctl".into(),
                        "lock".into(),
                        "mem".into(),
                        "pid".into(),
                        "proxy".into(),
                        "pty".into(),
                        "tpe".into(),
                    ],
                )]
                .into_iter()
                .collect::<SydHashMap<_, _>>(),
            ),
            ..Default::default()
        },
        Method {
            name: "rule_remove".into(),
            desc: "Remove an access control rule for sandboxing".into(),
            base: Some(vec![Arg::Action, Arg::Capability]),
            argv: Some(vec![Arg::Rule]),
            argc: Some(vec![1]),
            op: Some(Operator::Remove),
            exc: Some(
                [(
                    "$caps".into(),
                    vec![
                        "crypt".into(),
                        "force".into(),
                        "ioctl".into(),
                        "lock".into(),
                        "mem".into(),
                        "pid".into(),
                        "proxy".into(),
                        "pty".into(),
                        "tpe".into(),
                    ],
                )]
                .into_iter()
                .collect::<SydHashMap<_, _>>(),
            ),
            ..Default::default()
        },
        Method {
            name: "rule_clear".into(),
            desc: "Remove all matching access control rules for sandboxing".into(),
            base: Some(vec![Arg::Action, Arg::Capability]),
            argv: Some(vec![Arg::Rule]),
            argc: Some(vec![1]),
            op: Some(Operator::Clear),
            exc: Some(
                [(
                    "$caps".into(),
                    vec![
                        "crypt".into(),
                        "force".into(),
                        "ioctl".into(),
                        "lock".into(),
                        "mem".into(),
                        "pid".into(),
                        "proxy".into(),
                        "pty".into(),
                        "tpe".into(),
                    ],
                )]
                .into_iter()
                .collect::<SydHashMap<_, _>>(),
            ),
            ..Default::default()
        },
        Method {
            name: "mask_add".into(),
            desc: "Add a mask pattern for Read and Write sandboxing".into(),
            path: Some("mask".into()),
            argv: Some(vec![Arg::Glob, Arg::Path]),
            argc: Some(vec![1, 2]),
            op: Some(Operator::Append),
            ..Default::default()
        },
        Method {
            name: "mask_remove".into(),
            desc: "Remove a mask pattern for Read and Write sandboxing".into(),
            path: Some("mask".into()),
            argv: Some(vec![Arg::Glob]),
            argc: Some(vec![1]),
            op: Some(Operator::Remove),
            ..Default::default()
        },
        Method {
            name: "mask_clear".into(),
            desc: "Removes all mask patterns for Read and Write sandboxing".into(),
            path: Some("mask".into()),
            op: Some(Operator::Clear),
            ..Default::default()
        },
        Method {
            name: "mem_max".into(),
            desc: "Set maximum per-process memory usage limit for Memory sandboxing".into(),
            path: Some("mem/max".into()),
            argv: Some(vec![Arg::Hsize]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "mem_vm_max".into(),
            desc: "Set maximum per-process virtual memory usage limit for Memory sandboxing".into(),
            path: Some("mem/vm_max".into()),
            argv: Some(vec![Arg::Hsize]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "pid_max".into(),
            desc: "Set maximum process ID limit for PID sandboxing".into(),
            path: Some("pid/max".into()),
            argv: Some(vec![Arg::Usize]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "segvguard_expiry".into(),
            desc: "Set SegvGuard entry expiry timeout in seconds, 0 disables SegvGuard".into(),
            path: Some("segvguard/expiry".into()),
            argv: Some(vec![Arg::U64]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "segvguard_timeout".into(),
            desc: "Set SegvGuard entry suspension timeout in seconds".into(),
            path: Some("segvguard/suspension".into()),
            argv: Some(vec![Arg::U64]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "segvguard_maxcrashes".into(),
            desc: "Set SegvGuard max number of crashes before suspension".into(),
            path: Some("segvguard/maxcrashes".into()),
            argv: Some(vec![Arg::U8]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "tpe_gid".into(),
            desc: "Specify untrusted GID for Trusted Path Execution".into(),
            path: Some("tpe/gid".into()),
            argv: Some(vec![Arg::Gid, Arg::None]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "tpe_negate".into(),
            desc: "Negate GID logic for Trusted Path Execution".into(),
            path: Some("tpe/negate".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "tpe_root_owned".into(),
            desc: "Ensure directory is root-owned for Trusted Path Execution".into(),
            path: Some("tpe/root_owned".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "tpe_user_owned".into(),
            desc: "Ensure directory is user-owned or root-owned for Trusted Path Execution".into(),
            path: Some("tpe/user_owned".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "cmd_exec".into(),
            desc: "Execute a command outside the sandbox".into(),
            path: Some("cmd/exec".into()),
            argv: Some(vec![Arg::String]),
            argc: Some(vec![-1]),
            args: Some("\x1F".into()),
            op: Some(Operator::Exec),
            ..Default::default()
        },
        Method {
            name: "allow_unsafe_filename".into(),
            desc: "Allow unsafe characters in filenames".into(),
            path: Some("trace/allow_unsafe_filename".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_unsafe_hardlinks".into(),
            desc: "Allow unsafe access to hardlinks".into(),
            path: Some("trace/allow_unsafe_hardlinks".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_unsafe_proc_pid_status".into(),
            desc: "Allow unfiltered access to proc_pid_status(5)".into(),
            path: Some("trace/allow_unsafe_proc_pid_status".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_unsafe_magiclinks".into(),
            desc: "Allow unsafe access to procfs magiclinks".into(),
            path: Some("trace/allow_unsafe_magiclinks".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_unsafe_nopie".into(),
            desc: "Allow unsafe execution of non-PIE binaries".into(),
            path: Some("trace/allow_unsafe_nopie".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_unsafe_open_path".into(),
            desc: "Allow unsafe continue of O_PATH opens".into(),
            path: Some("trace/allow_unsafe_open_path".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_unsafe_open_kfd".into(),
            desc: "Allow unsafe continue of AMD KFD character device opens".into(),
            path: Some("trace/allow_unsafe_open_kfd".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_unsafe_xattr".into(),
            desc: "Allow unsafe access to sensitive extensive attributes".into(),
            path: Some("trace/allow_unsafe_open_xattr".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_safe_kcapi".into(),
            desc: "Allow safe access to kernel cryptography API".into(),
            path: Some("trace/allow_safe_kcapi".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_unsupp_socket".into(),
            desc: "Allow access to unsupported socket families".into(),
            path: Some("trace/allow_unsupp_socket".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "allow_unsafe_memfd".into(),
            desc: "Allow unsafe access to memory file descriptors".into(),
            path: Some("trace/allow_unsafe_memfd".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "deny_dotdot".into(),
            desc: "Deny .. components in path resolution".into(),
            path: Some("trace/deny_dotdot".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "deny_elf32".into(),
            desc: "Deny execution of 32-bit ELF binaries".into(),
            path: Some("trace/deny_elf32".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "deny_elf_dynamic".into(),
            desc: "Deny execution of dynamically linked ELF binaries".into(),
            path: Some("trace/deny_elf_dynamic".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "deny_elf_static".into(),
            desc: "Deny execution of statically linked ELF binaries".into(),
            path: Some("trace/deny_elf_static".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "deny_script".into(),
            desc: "Deny execution of scripts".into(),
            path: Some("trace/deny_script".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "force_cloexec".into(),
            desc: "Force O_CLOEXEC flag on file descriptors".into(),
            path: Some("trace/force_cloexec".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "force_rand_fd".into(),
            desc: "Force randomized file descriptors".into(),
            path: Some("trace/force_rand_fd".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "force_ro_open".into(),
            desc: "Deny creating and writing opens".into(),
            path: Some("trace/force_ro_open".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "force_no_symlinks".into(),
            desc: "Force RESOLVE_NO_SYMLINKS at open(2) boundary".into(),
            path: Some("trace/force_no_symlinks".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "force_no_magiclinks".into(),
            desc: "Force RESOLVE_NO_MAGICLINKS at open(2) boundary".into(),
            path: Some("trace/force_no_magiclinks".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "force_no_xdev".into(),
            desc: "Force RESOLVE_NO_XDEV at open(2) boundary".into(),
            path: Some("trace/force_no_xdev".into()),
            argv: Some(vec![Arg::Boolean]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
        Method {
            name: "force_umask".into(),
            desc: "Force given umask mode, -1 to unset".into(),
            path: Some("trace/force_umask".into()),
            argv: Some(vec![Arg::Mode, Arg::MinusOne]),
            argc: Some(vec![1]),
            op: Some(Operator::Set),
            ..Default::default()
        },
    ],
    types: vec![
        Type {
            name: "$str".into(),
            desc: "UTF-8 encoded string".into(),
            fmt: Some("utf-8".into()),
            ..Default::default()
        },
        Type {
            name: "$bool".into(),
            desc: "Boolean".into(),
            enums: Some(vec!["true".into(), "false".into()]),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$u8".into(),
            desc: "8-bit unsigned integer".into(),
            fmt: Some("int".into()),
            limit: Some((u64::from(u8::MIN), u64::from(u8::MAX))),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$usize".into(),
            desc: "Pointer-sized unsigned integer".into(),
            fmt: Some("int".into()),
            limit: Some((usize::MIN as u64, usize::MAX as u64)),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$u64".into(),
            desc: "64-bit unsigned integer".into(),
            fmt: Some("int".into()),
            limit: Some((u64::MIN, u64::MAX)),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$uid".into(),
            desc: "User ID type (unsigned integer)".into(),
            fmt: Some("int".into()),
            limit: Some((u64::from(libc::uid_t::MIN), u64::from(libc::uid_t::MAX))),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$gid".into(),
            desc: "Group ID type (unsigned integer)".into(),
            fmt: Some("int".into()),
            limit: Some((u64::from(libc::gid_t::MIN), u64::from(libc::gid_t::MAX))),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$mode".into(),
            desc: "Mode in octal integer literal (base-8)".into(),
            fmt: Some("oct".into()),
            limit: Some((u64::from(libc::mode_t::MIN), u64::from(libc::mode_t::MAX))),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$path".into(),
            desc: "Absolute pathname".into(),
            limit: Some((1, PATH_MAX as u64)),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$hsize".into(),
            desc: "Human-formatted size".into(),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$state".into(),
            desc: "Sandbox lock state".into(),
            enums: Some(vec!["on".into(), "off".into(), "exec".into(), "ipc".into()]),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$action".into(),
            desc: "Sandbox action".into(),
            enums: Some(ACTION_STR.iter().map(|p| p.to_string()).collect::<Vec<_>>()),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$caps".into(),
            desc: "Sandbox capabilities".into(),
            enums: Some(
                SANDBOX_CAPS
                    .keys()
                    .map(|p| p.to_string())
                    .collect::<Vec<_>>(),
            ),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$cidr".into(),
            desc: "CIDR pattern for IP blocklist".into(),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$glob".into(),
            desc: "Glob pattern, see PATTERN MATCHING in syd(2)".into(),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$addr".into(),
            desc: "Address pattern, see ADDRESS MATCHING in syd(2)".into(),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$rule".into(),
            desc: "Sandbox rule".into(),
            impls: Some(vec!["$glob".into(), "$addr".into()]),
            ..Default::default()
        },
        Type {
            name: "$filedes".into(),
            desc: "File descriptor".into(),
            limit: Some((0, RawFd::MAX as u64)),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$profile".into(),
            desc: "Sandbox profile".into(),
            enums: Some(vec![
                "container".into(),
                "core".into(),
                "debug".into(),
                "enforce".into(),
                "ff".into(),
                "firefox".into(),
                "immutable".into(),
                "kvm".into(),
                "kvm_native".into(),
                "landlock".into(),
                "lib".into(),
                "linux".into(),
                "noipv4".into(),
                "noipv6".into(),
                "nomem".into(),
                "nopie".into(),
                "oci".into(),
                "off".into(),
                "paludis".into(),
                "privileged".into(),
                "quiet".into(),
                "readonly".into(),
                "ro".into(),
                "silent".into(),
                "trace".into(),
                "tty".into(),
                "tty_native".into(),
                "user".into(),
            ]),
            impls: Some(vec!["$str".into()]),
            ..Default::default()
        },
        Type {
            name: "$hash".into(),
            desc: "Hexadecimal file checksum".into(),
            impls: Some(vec![
                "$crc32".into(),
                "$crc64".into(),
                "$md5".into(),
                "$sha1".into(),
                "$sha3_256".into(),
                "$sha3_384".into(),
                "$sha3_512".into(),
            ]),
            ..Default::default()
        },
        Type {
            name: "$crc32".into(),
            desc: "Hexadecimal CRC32 checksum".into(),
            fmt: Some("hex".into()),
            impls: Some(vec!["$str".into()]),
            limit: Some((8, 8)),
            ..Default::default()
        },
        Type {
            name: "$crc64".into(),
            desc: "Hexadecimal CRC64 checksum".into(),
            fmt: Some("hex".into()),
            impls: Some(vec!["$str".into()]),
            limit: Some((16, 16)),
            ..Default::default()
        },
        Type {
            name: "$md5".into(),
            desc: "Hexadecimal MD5 checksum".into(),
            fmt: Some("hex".into()),
            impls: Some(vec!["$str".into()]),
            limit: Some((32, 32)),
            ..Default::default()
        },
        Type {
            name: "$sha1".into(),
            desc: "Hexadecimal SHA1 checksum".into(),
            fmt: Some("hex".into()),
            impls: Some(vec!["$str".into()]),
            limit: Some((40, 40)),
            ..Default::default()
        },
        Type {
            name: "$sha3_256".into(),
            desc: "Hexadecimal SHA3-256 checksum".into(),
            fmt: Some("hex".into()),
            impls: Some(vec!["$str".into()]),
            limit: Some((64, 64)),
            ..Default::default()
        },
        Type {
            name: "$sha3_384".into(),
            desc: "Hexadecimal SHA3-384 checksum".into(),
            fmt: Some("hex".into()),
            impls: Some(vec!["$str".into()]),
            limit: Some((96, 96)),
            ..Default::default()
        },
        Type {
            name: "$sha3_512".into(),
            desc: "Hexadecimal SHA3-512 checksum".into(),
            fmt: Some("hex".into()),
            impls: Some(vec!["$str".into()]),
            limit: Some((128, 128)),
            ..Default::default()
        },
    ],
});

/// The root of the exported API spec.
pub struct Api {
    root: String,
    version: Version,
    methods: Vec<Method>,
    types: Vec<Type>,
}

impl Serialize for Api {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(1))?;
        map.serialize_entry("root", &self.root)?;
        map.serialize_entry("version", &self.version)?;
        map.serialize_entry("methods", &self.methods)?;
        map.serialize_entry("types", &self.types)?;
        map.end()
    }
}

// A single `/dev/syd` command binding.
#[derive(Debug, Default)]
struct Method {
    // Method name (e.g. `enable_stat`).
    name: String,
    // Method description.
    desc: String,
    // Method operator (`:`, `?`, `+`, `-`, `^`, or `!`).
    op: Option<Operator>,
    // The directory segment under /dev/syd
    path: Option<String>,
    // The base segment under /dev/syd, may be comma-separated.
    base: Option<Vec<Arg>>,
    // Number of arguments.
    argc: Option<Vec<i8>>,
    // Typed arguments to interpolate after the operator.
    argv: Option<Vec<Arg>>,
    // Argument separator.
    args: Option<String>,
    // Exclusions for argument validations
    exc: Option<SydHashMap<String, Vec<String>>>,
}

impl Serialize for Method {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(6))?;
        map.serialize_entry("name", &self.name)?;
        map.serialize_entry("desc", &self.desc)?;
        if let Some(ref path) = self.path {
            map.serialize_entry("path", path)?;
        }
        if let Some(ref base) = self.base {
            if base.len() == 1 {
                map.serialize_entry("base", &base[0])?;
            } else {
                map.serialize_entry("base", base)?;
            }
        }
        if let Some(ref argv) = self.argv {
            if argv.len() == 1 {
                map.serialize_entry("argv", &argv[0])?;
            } else {
                map.serialize_entry("argv", argv)?;
            }
        }
        if let Some(ref argc) = self.argc {
            if argc.len() == 1 {
                map.serialize_entry("argc", &argc[0])?;
            } else {
                map.serialize_entry("argc", argc)?;
            }
        }
        if let Some(ref args) = self.args {
            map.serialize_entry("args", args)?;
        }
        if let Some(ref op) = self.op {
            map.serialize_entry("op", op)?;
        }
        if let Some(ref exc) = self.exc {
            map.serialize_entry("exc", exc)?;
        }

        map.end()
    }
}

// API types
#[derive(Debug, Default)]
struct Type {
    name: String,
    desc: String,
    fmt: Option<String>,
    enums: Option<Vec<String>>,
    impls: Option<Vec<String>>,
    limit: Option<(u64, u64)>,
}

impl Serialize for Type {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(5))?;
        map.serialize_entry("name", &self.name)?;
        map.serialize_entry("desc", &self.desc)?;
        if let Some((min, max)) = self.limit {
            map.serialize_entry("min", &min)?;
            map.serialize_entry("max", &max)?;
        }
        if let Some(ref fmt) = self.fmt {
            map.serialize_entry("fmt", fmt)?;
        }
        if let Some(ref enums) = self.enums {
            if enums.len() == 1 {
                map.serialize_entry("enum", &enums[0])?;
            } else {
                map.serialize_entry("enum", enums)?;
            }
        }
        if let Some(ref impls) = self.impls {
            if impls.len() == 1 {
                map.serialize_entry("impl", &impls[0])?;
            } else {
                map.serialize_entry("impl", impls)?;
            }
        }
        map.end()
    }
}

// Which `/dev/syd` operator to emit.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Operator {
    Set,    // `:`
    Query,  // `?`
    Append, // `+`
    Remove, // `-`
    Clear,  // `^`
    Exec,   // `!`
}

impl Serialize for Operator {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = match self {
            Self::Set => ":",
            Self::Query => "?",
            Self::Append => "+",
            Self::Remove => "-",
            Self::Clear => "^",
            Self::Exec => "!",
        };
        serializer.serialize_str(s)
    }
}

// The possible argument types.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Arg {
    MinusOne, // `-1`, used to unset.
    None,     // `none`, used to unset.
    Glob,     // glob(3) pattern
    Cidr,     // CIDR pattern (without port)
    //Addr,       // Address pattern (with port)
    Path,   // filesystem path
    String, // arbitrary string
    U64,    // u64
    Usize,  // usize
    U8,     // u8
    //Uid,        // uid_t
    Gid,        // gid_t
    Hsize,      // human size
    Mode,       // Mode in octal integer literal (base-8)
    Boolean,    // true/false
    Hash,       // hex-encoded checksum
    Action,     // sandbox action
    Capability, // sandbox capability
    Rule,       // sandbox rule
    State,      // lock state
    FileDes,    // file descriptor
    Profile,    // builtin profile
}

impl Serialize for Arg {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Self::MinusOne => serializer.serialize_str("-1"),
            Self::None => serializer.serialize_str("none"),
            Self::Glob => serializer.serialize_str("$glob"),
            Self::Cidr => serializer.serialize_str("$cidr"),
            //Self::Addr => serializer.serialize_str("$addr"),
            Self::Path => serializer.serialize_str("$path"),
            Self::String => serializer.serialize_str("$str"),
            Self::U64 => serializer.serialize_str("$u64"),
            Self::Usize => serializer.serialize_str("$usize"),
            Self::U8 => serializer.serialize_str("$u8"),
            //Self::Uid => serializer.serialize_str("$uid"),
            Self::Gid => serializer.serialize_str("$gid"),
            Self::Hsize => serializer.serialize_str("$hsize"),
            Self::Mode => serializer.serialize_str("$mode"),
            Self::Boolean => serializer.serialize_str("$bool"),
            Self::Hash => serializer.serialize_str("$hash"),
            Self::Action => serializer.serialize_str("$action"),
            Self::Capability => serializer.serialize_str("$caps"),
            Self::Rule => serializer.serialize_str("$rule"),
            Self::State => serializer.serialize_str("$state"),
            Self::FileDes => serializer.serialize_str("$filedes"),
            Self::Profile => serializer.serialize_str("$profile"),
        }
    }
}
