use {
    crate::{
        archive::{Archive, ArchiveArgs, OptionalArchiveArgs, SnapshotId, SnapshotIdArgParser},
        content::ContentProvider,
        packages::{InstallPriority, Package},
        version::{Constraint, Dependency, Version},
        StagingFileSystem,
    },
    anyhow::Result,
    indicatif::ProgressBar,
    std::{
        ffi::OsString,
        io::{self, Write},
        num::NonZero,
        path::{Component, Path},
        str::FromStr,
        sync::{
            atomic::{AtomicU64, AtomicU8},
            Arc,
        },
    },
};

pub trait Config {
    type FS: StagingFileSystem;
    type Cache: ContentProvider<Target = Self::FS>;
    fn log_level(&self) -> i32 {
        0
    }
    fn arch(&self) -> &str;
    fn manifest(&self) -> &Path;
    fn concurrency(&self) -> NonZero<usize>;
    fn fetcher(&self) -> io::Result<&Self::Cache>;
}

pub trait Command<C> {
    fn exec(&self, conf: &C) -> Result<()>;
}

#[derive(Clone)]
pub enum StageProgress {
    Indicatif(ProgressBar),
    Percent(PercentProgress),
}

impl StageProgress {
    pub fn from_progress_bar(pb: ProgressBar) -> Self {
        Self::Indicatif(pb)
    }

    pub fn percent(total: u64) -> Self {
        Self::Percent(PercentProgress::new(total))
    }

    pub(crate) fn inc(&self, delta: u64) {
        match self {
            StageProgress::Indicatif(pb) => pb.inc(delta),
            StageProgress::Percent(progress) => progress.inc(delta),
        }
    }

    pub(crate) fn finish(&self) {
        match self {
            StageProgress::Indicatif(pb) => pb.finish_using_style(),
            StageProgress::Percent(progress) => progress.finish(),
        }
    }
}

/// Simple percentage progress reporter for non-interactive output.
pub struct PercentProgress {
    inner: Arc<PercentProgressInner>,
}
impl Clone for PercentProgress {
    fn clone(&self) -> Self {
        Self {
            inner: Arc::clone(&self.inner),
        }
    }
}

struct PercentProgressInner {
    total: u64,
    current: AtomicU64,
    last: AtomicU8,
}

impl PercentProgress {
    fn new(total: u64) -> Self {
        {
            use std::io::Write as _;
            let mut stdout = std::io::stdout();
            let _ = stdout.write_all(b"... ");
            let _ = stdout.flush();
        }
        Self {
            inner: Arc::new(PercentProgressInner {
                total,
                current: AtomicU64::new(0),
                last: AtomicU8::new(0),
            }),
        }
    }

    fn inc(&self, delta: u64) {
        use std::sync::atomic::Ordering;
        let current = self
            .inner
            .current
            .fetch_add(delta, Ordering::Relaxed)
            .saturating_add(delta);
        self.print_thresholds(self.percent(current));
    }

    fn finish(&self) {
        self.print_thresholds(100);
    }

    fn percent(&self, current: u64) -> u8 {
        current
            .saturating_mul(100)
            .checked_div(self.inner.total)
            .unwrap_or(100)
            .min(100) as u8
    }

    fn print_thresholds(&self, percent: u8) {
        use std::sync::atomic::Ordering;
        let last = self.inner.last.load(Ordering::Relaxed);
        let next = last + 10;
        if percent > next
            && self
                .inner
                .last
                .compare_exchange(last, next, Ordering::AcqRel, Ordering::Acquire)
                .is_ok()
        {
            use std::io::Write as _;
            let mut stdout = std::io::stdout();
            let _ = writeln!(stdout, "{}%", next);
            if next != 100 {
                let _ = write!(stdout, "... ");
            }
            let _ = stdout.flush();
        }
    }
}

/// CLI subcommand definitions and argument parsing.
pub mod cmd {
    pub use crate::cli_edit::Edit;
    use {
        super::*,
        crate::{
            archive::SignedBy,
            artifact::{hash_directory, ArtifactArg},
            builder::ExecutorKind,
            comp::is_comp_ext,
            content::{ContentProviderGuard, HostCache},
            hash::{Hash, HashAlgo},
            manifest::Manifest,
            staging::HostFileSystem,
            version::ProvidedName,
        },
        anyhow::{anyhow, Result},
        clap::Parser,
        indicatif::ProgressBar,
        itertools::Itertools,
        rustix::path::Arg,
        std::{
            io::IsTerminal,
            path::{Path, PathBuf},
        },
    };

    #[derive(Parser)]
    #[command(
        about = "Create a new manifest file",
        long_about = r#"Create a new manifest file from an archive definition, vendor preset, or imported manifest.
If a vendor name is provided as the archive URL, default archives and packages are derived from it.
Use --import PATH to bootstrap from another already-locked manifest instead of defining local archives.
Examples:
    rdebootstrap init debian --package mc --package libcom-err2
    rdebootstrap init --import ../base/Manifest.toml --spec base"#
    )]
    /// CLI command: initialize a new repository manifest.
    pub struct Init {
        /// Overwrite existing manifest if present
        #[arg(long)]
        pub force: bool,

        /// Comment to add to the manifest file
        #[arg(short = 'c', long = "comment", value_name = "COMMENT")]
        comment: Option<String>,

        /// Package to add (can be used multiple times)
        #[arg(short = 'r', long = "package", value_name = "PACKAGE")]
        requirements: Vec<String>,

        /// Meta entry to add to the default spec (NAME VALUE, repeatable)
        #[arg(
            long = "meta",
            value_names = ["NAME", "VALUE"],
            num_args = 2,
            action = clap::ArgAction::Append
        )]
        meta: Vec<String>,

        /// Import another already-locked manifest instead of defining local archives
        #[arg(
            long = "import",
            value_name = "PATH",
            conflicts_with_all = [
                "url",
                "arch",
                "allow_insecure",
                "signed_by",
                "snapshots",
                "snapshot",
                "suites",
                "components",
                "hash",
                "priority",
            ]
        )]
        import: Option<PathBuf>,

        /// Spec name to export from the imported manifest (repeatable, optional)
        #[arg(
            long = "spec",
            value_name = "SPEC",
            value_delimiter = ',',
            requires = "import"
        )]
        specs: Vec<String>,

        #[command(flatten)]
        archive: OptionalArchiveArgs,

        /// Do not verify InRelease signatures by default (not recommended)
        #[arg(
            long = "no-verify",
            display_order = 0,
            action,
            conflicts_with = "import"
        )]
        insecure_release: bool,
    }

    impl<C: Config> Command<C> for Init {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let mut mf = if let Some(path) = self.import.as_deref() {
                    let mut mf =
                        Manifest::new(conf.manifest(), conf.arch(), self.comment.as_deref());
                    let path = rebase_pathbuf_input(path, conf.manifest())?;
                    mf.set_import(&path, &self.specs).await?;
                    mf
                } else {
                    let mut archive = self.archive.to_archive().ok_or_else(|| {
                        anyhow!("either an archive/vendor template or --import PATH is required")
                    })?;
                    archive.signed_by =
                        rebase_signed_by_input(&archive.signed_by, conf.manifest())?;
                    let (archives, packages, comment) =
                        if let Some((archives, mut packages)) = archive.as_vendor() {
                            if !self.requirements.is_empty() {
                                packages.extend(self.requirements.iter().cloned());
                                packages = packages.into_iter().unique().collect();
                            }
                            (
                                archives,
                                packages,
                                Some(format!("default manifest file for {}", &archive.url)),
                            )
                        } else {
                            (vec![archive], self.requirements.clone(), None)
                        };
                    let mut mf = Manifest::from_archives(
                        conf.manifest(),
                        conf.arch(),
                        archives.iter().cloned(),
                        self.comment.as_deref().or(comment.as_deref()),
                    );
                    mf.add_requirements(None, packages.iter(), None)?;
                    mf
                };
                for pair in self.meta.chunks_exact(2) {
                    mf.set_spec_meta(None, &pair[0], &pair[1])?;
                }
                if self.import.is_some() {
                    mf.add_requirements(None, self.requirements.iter(), None)?;
                }
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                if self.import.is_some() {
                    mf.resolve(conf.concurrency(), fetcher).await?;
                } else {
                    mf.update(
                        true,
                        false,
                        self.insecure_release,
                        conf.concurrency(),
                        fetcher,
                    )
                    .await?;
                }
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Add an archive",
        long_about = "Add an archive definition to the manifest file."
    )]
    /// CLI command: add an archive to the manifest.
    pub struct ArchiveAdd {
        /// Optional comment to record with this change
        #[arg(short = 'c', long = "comment", value_name = "COMMENT")]
        comment: Option<String>,
        /// Do not verify InRelease signatures by default (not recommended)
        #[arg(long = "no-verify", display_order = 0, action)]
        insecure_release: bool,

        #[command(flatten)]
        archive: ArchiveArgs,
    }
    impl<C: Config> Command<C> for ArchiveAdd {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                let mut archive = Archive::from(&self.archive);
                archive.signed_by = rebase_signed_by_input(&archive.signed_by, conf.manifest())?;
                if let Some((archives, _)) = archive.as_vendor() {
                    for archive in archives {
                        mf.add_archive(archive, self.comment.as_deref())?;
                    }
                } else {
                    mf.add_archive(archive, self.comment.as_deref())?;
                }
                mf.update(false, false, false, conf.concurrency(), fetcher)
                    .await?;
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Add a local package",
        long_about = "Add a local .deb package to the manifest file so it can be staged alongside archives."
    )]
    /// CLI command: add a local package file to the manifest.
    pub struct DebAdd {
        /// Optional comment to record with this change
        #[arg(short = 'c', long = "comment", value_name = "COMMENT")]
        comment: Option<String>,
        /// Path to a local .deb file
        #[arg(value_name = "PATH")]
        path: PathBuf,
    }
    impl<C: Config> Command<C> for DebAdd {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let path = self
                    .path
                    .as_str()
                    .map_err(|err| anyhow!("invalid path: {}", err))?;
                let path = rebase_cli_local_input(path, conf.manifest())?;
                let (mut mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                let base = mf.local_path(&path);
                let (file, ctrl) = fetcher.ensure_deb(&path, &base).await?;
                mf.add_local_package(file, ctrl, self.comment.as_deref())?;
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Add an artifact",
        long_about = "Add an artifact definition to the manifest file. Use --stage to attach it to a spec."
    )]
    /// CLI command: add an artifact definition to the manifest.
    pub struct ArtifactAdd {
        /// Optional comment to record with this change
        #[arg(short = 'c', long = "comment", value_name = "COMMENT")]
        comment: Option<String>,

        /// Stage the artifact into a spec (default spec if --spec is omitted)
        #[arg(long = "stage", action)]
        stage: bool,

        /// Target spec (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,

        #[command(flatten)]
        artifact: ArtifactArg,
    }
    impl<C: Config> Command<C> for ArtifactAdd {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                if let Some(path) = self.artifact.url.strip_prefix('@') {
                    if path.is_empty() {
                        return Err(anyhow!("text artifact path is empty"));
                    }
                    let name = Path::new(path)
                        .file_name()
                        .and_then(|name| name.to_str())
                        .ok_or_else(|| anyhow!("text artifact path has no filename: {path}"))?;
                    let text = std::fs::read(path)
                        .map_err(|err| anyhow!("failed to read text artifact {}: {}", path, err))
                        .and_then(|bytes| {
                            String::from_utf8(bytes)
                                .map_err(|_| anyhow!("text artifact {} is not valid utf-8", path))
                        })?;
                    let target =
                        self.artifact.target.clone().ok_or_else(|| {
                            anyhow!("text artifact {} requires TARGET_PATH", path)
                        })?;
                    mf.upsert_text_artifact(
                        name,
                        target,
                        text,
                        self.artifact.mode,
                        self.artifact.target_arch.clone(),
                    )?;
                    if self.stage {
                        mf.add_stage_items(self.spec.as_deref(), vec![name.to_string()], None)?;
                    }
                } else {
                    let mut artifact = ArtifactArg {
                        mode: self.artifact.mode,
                        do_not_unpack: self.artifact.do_not_unpack,
                        target_arch: self.artifact.target_arch.clone(),
                        url: self.artifact.url.clone(),
                        target: self.artifact.target.clone(),
                    };
                    if !crate::is_url(&artifact.url) {
                        artifact.url = rebase_cli_local_input(&artifact.url, conf.manifest())?;
                    }
                    mf.upsert_artifact_only(&artifact, self.comment.as_deref(), fetcher)
                        .await?;
                    if self.stage {
                        mf.add_stage_items(self.spec.as_deref(), vec![artifact.url], None)?;
                    }
                }
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Remove an archive",
        long_about = "Remove an archive reference from the manifest file."
    )]
    /// CLI command: remove an archive from the manifest.
    pub struct ArchiveRemove {
        #[command(flatten)]
        archive: ArchiveArgs,
    }
    impl<C: Config> Command<C> for ArchiveRemove {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                let archive = Archive::from(&self.archive);
                if let Some((archives, _)) = archive.as_vendor() {
                    for archive in archives {
                        mf.drop_archive(&archive.url)?;
                    }
                } else {
                    mf.drop_archive(&archive.url)?;
                }
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Remove local package",
        long_about = "Remove a local package from the manifest file."
    )]
    /// CLI command: remove a local package from the manifest.
    pub struct DebRemove {
        #[arg(value_name = "PATH")]
        path: PathBuf,
    }
    impl<C: Config> Command<C> for DebRemove {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let path = self
                    .path
                    .as_str()
                    .map_err(|err| anyhow!("invalid path: {}", err))?;
                let path = rebase_cli_local_input(path, conf.manifest())?;
                let (mut mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                mf.drop_local_package(&path)?;
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        alias = "drop",
        about = "Remove requirements or constraints from a spec",
        long_about = r#"Remove requirements and/or constraints from a spec.
Use --requirements-only or --constraints-only to limit the operation scope."#
    )]
    /// CLI command: drop entries from the manifest lock.
    pub struct Remove {
        /// Drop only requirements (do not touch constraints)
        #[arg(
            short = 'R',
            long = "requirements-only",
            conflicts_with = "constraints_only"
        )]
        requirements_only: bool,

        /// Drop only constraints (do not touch requirements)
        #[arg(
            short = 'C',
            long = "constraints-only",
            conflicts_with = "requirements_only"
        )]
        constraints_only: bool,

        /// The spec name to modify (none for the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,

        /// Package name or package version set
        #[arg(value_name = "PACKAGE_OR_SET")]
        cons: Vec<String>,
    }
    impl<C: Config> Command<C> for Remove {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !self.constraints_only {
                    mf.remove_requirements(self.spec.as_deref(), self.cons.iter())?;
                }
                if !self.requirements_only {
                    mf.remove_constraints(self.spec.as_deref(), self.cons.iter())?;
                }
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Stage an artifact into a spec",
        long_about = "Add an external artifact (URL or file path) to a spec so it is included in the system tree."
    )]
    /// CLI command: stage packages into a tree.
    pub struct Stage {
        /// Target spec (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,

        /// A comment for the staged artifact entry
        #[arg(short = 'c', long = "comment", value_name = "COMMENT")]
        comment: Option<String>,

        /// Artifact name or URI
        #[arg(value_name = "ARTIFACT")]
        artifact: String,
    }

    impl<C: Config> Command<C> for Stage {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let (mut mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                let artifact = if crate::is_url(&self.artifact) {
                    self.artifact.clone()
                } else {
                    rebase_cli_local_input(&self.artifact, conf.manifest())?
                };
                mf.add_stage_items(
                    self.spec.as_deref(),
                    vec![artifact],
                    self.comment.as_deref(),
                )?;
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Remove a staged artifact from a spec",
        long_about = "Remove a previously staged artifact (by URL or file path) from the given spec."
    )]
    /// CLI command: remove staged packages from a tree.
    pub struct Unstage {
        /// Target spec (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,
        /// Artifact URL or path
        #[arg(value_name = "URL")]
        url: String,
    }

    impl<C: Config> Command<C> for Unstage {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let (mut mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                let url = if crate::is_url(&self.url) {
                    self.url.clone()
                } else {
                    rebase_cli_local_input(&self.url, conf.manifest())?
                };
                mf.remove_artifact(self.spec.as_deref(), &url)?;
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Add package requirements to a spec",
        alias = "include",
        long_about = r#"Add one or more package requirements to a spec. Each requirement can be a bare package name or a set, and can include a version relation, e.g.:
  foo
  foo (= 1.2.3)
  bar (>= 2.0)
  foo | bar (<< 3.0)

  Alternatively, requirement might be a path to .deb file to include directly."#
    )]
    /// CLI command: include packages into the spec.
    pub struct Require {
        /// Target spec (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,
        /// Optional comment to record with this change
        #[arg(short = 'c', long = "comment", value_name = "COMMENT")]
        comment: Option<String>,
        /// Requirement(s) to include (repeatable)
        #[arg(value_name = "REQUIREMENT", value_parser = DependencyParser)]
        reqs: Vec<Dependency<String>>,
    }

    impl<C: Config> Command<C> for Require {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                mf.add_requirements(
                    self.spec.as_deref(),
                    self.reqs.iter(),
                    self.comment.as_deref(),
                )?;
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Add package constraints to a spec",
        alias = "exclude",
        long_about = r#"Add one or more constraints to restrict resolution. Examples:
  foo (= 1.2.3)
  foo (<< 2.0)  
  bar (<= 3.4)"#
    )]
    /// CLI command: exclude packages from the spec.
    pub struct Forbid {
        /// Target spec (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,
        /// Optional comment to record with this change
        #[arg(short = 'c', long = "comment", value_name = "COMMENT")]
        comment: Option<String>,
        /// Constraint(s) to apply (repeatable)
        #[arg(value_name = "CONSTRAINT", value_parser = ConstraintParser)]
        reqs: Vec<Constraint<String>>,
    }

    impl<C: Config> Command<C> for Forbid {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                mf.add_constraints(
                    self.spec.as_deref(),
                    self.reqs.iter(),
                    self.comment.as_deref(),
                )?;
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Update archives and snapshot/lock state",
        long_about = "Refresh archive metadata, solve specs, rewrite the lock file, and refresh stored import fingerprints when [import] is present."
    )]
    /// CLI command: update the lockfile from sources.
    pub struct Update {
        /// Update package archives.
        #[arg(short = 'A', long = "archives", action)]
        archives: bool,
        /// Refresh local packages and local artifacts
        #[arg(short = 'L', long = "locals", action)]
        locals: bool,
        /// Snapshot ID to use for all snapshot-enabled archives (use 'now' for current time).
        ///
        /// Snapshot ID to use for all snapshot-enabled archives (use 'now' for current time), implies --archives
        #[arg(short = 's', long = "snapshot", value_name = "SNAPSHOT_ID", value_parser = SnapshotIdArgParser)]
        snapshot: Option<SnapshotId>,
        /// Do not verify InRelease signatures by default (not recommended)
        #[arg(long = "no-verify", display_order = 0, action)]
        insecure_release: bool,
    }

    impl<C: Config> Command<C> for Update {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                let force_archives = self.archives || self.snapshot.is_some();
                let force = force_archives || self.locals;
                if has_valid_lock && !force {
                    tracing::debug!("Lock file is up to date, nothing to do");
                    return Ok(());
                }
                if let Some(snapshot) = &self.snapshot {
                    mf.set_snapshot(*snapshot).await;
                }
                mf.update(
                    force_archives,
                    self.locals,
                    self.insecure_release,
                    conf.concurrency(),
                    fetcher,
                )
                .await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Add or replace an imported manifest",
        long_about = "Configure a manifest import: validates an already-locked imported manifest, optionally exports selected named specs for downstream extends, and writes [import] to the downstream manifest."
    )]
    /// CLI command: configure a manifest import.
    pub struct ImportCmd {
        /// Path to the manifest file to import (absolute or relative to current working directory)
        #[arg(value_name = "PATH")]
        path: PathBuf,

        /// Spec name to export from the imported manifest (repeatable, optional)
        #[arg(long = "spec", value_name = "SPEC", value_delimiter = ',')]
        specs: Vec<String>,
    }

    impl<C: Config> Command<C> for ImportCmd {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let (mut mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                let path = rebase_pathbuf_input(&self.path, conf.manifest())?;
                mf.set_import(&path, &self.specs).await?;
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                mf.resolve(conf.concurrency(), conf.fetcher()?).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Search packages in archives",
        long_about = "Search for packages matching the given regex pattern(s)."
    )]
    /// CLI command: search packages in the universe.
    pub struct Search {
        /// Match only package names (ignore descriptions)
        #[arg(short = 'p', long = "names-only")]
        names_only: bool,
        /// Regex pattern(s) to search for (repeatable)
        #[arg(value_name = "PATTERN")]
        pattern: Vec<String>,
    }

    impl<C: Config> Command<C> for Search {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                mf.load_universe(conf.concurrency(), fetcher).await?;
                guard.commit().await?;
                let res = self
                    .pattern
                    .iter()
                    .map(|p| {
                        regex::RegexBuilder::new(p)
                            .unicode(true)
                            .case_insensitive(true)
                            .build()
                            .map_err(|err| anyhow!("invalid regex: {}", err))
                    })
                    .collect::<Result<Vec<_>>>()?;
                let mut pkgs = mf
                    .universe_packages()?
                    .filter(|p| {
                        res.is_empty()
                            || res.iter().any(|re| {
                                re.is_match(p.name())
                                    || (!self.names_only
                                        && re.is_match(p.field("Description").unwrap_or("")))
                            })
                    })
                    .collect::<Vec<_>>();
                pkgs.sort_by_key(|&pkg| pkg.name());
                let out = pretty_print_packages(pkgs, false)?;
                std::io::stdout().lock().write_all(&out)?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    pub enum SpecCommands {
        Meta(SpecMeta),
        Hash(SpecHash),
        List(SpecList),
        Packages(SpecPackages),
        Require(SpecRequire),
        Forbid(SpecForbid),
        Remove(SpecRemove),
        Extend(SpecExtend),
        Artifact(SpecArtifact),
        InstallOrder(SpecInstallOrder),
    }

    #[derive(Parser)]
    #[command(about = "Inspect or update specs")]
    /// CLI command: inspect or update spec data.
    pub struct Spec {
        #[command(subcommand)]
        cmd: SpecCommands,
    }
    impl<C: Config> Command<C> for Spec {
        fn exec(&self, conf: &C) -> Result<()> {
            match &self.cmd {
                SpecCommands::Meta(cmd) => cmd.exec(conf),
                SpecCommands::Hash(cmd) => cmd.exec(conf),
                SpecCommands::List(cmd) => cmd.exec(conf),
                SpecCommands::Packages(cmd) => cmd.exec(conf),
                SpecCommands::Require(cmd) => cmd.exec(conf),
                SpecCommands::Forbid(cmd) => cmd.exec(conf),
                SpecCommands::Remove(cmd) => cmd.exec(conf),
                SpecCommands::Extend(cmd) => cmd.exec(conf),
                SpecCommands::Artifact(cmd) => cmd.exec(conf),
                SpecCommands::InstallOrder(cmd) => cmd.exec(conf),
            }
        }
    }

    #[derive(Parser)]
    #[command(about = "List packages installation order for a spec", hide = true)]
    /// CLI command: List spec installation order
    pub struct SpecInstallOrder {
        /// Spec name (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,
        /// Flat the spec hierarchy and list all effective packages, including inherited
        #[arg(long = "flat", action)]
        flat: bool,
    }

    impl<C: Config> Command<C> for SpecInstallOrder {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let (mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                let spec = mf.lookup_spec(self.spec.as_deref())?;
                let (_, essentials, other) = if self.flat {
                    spec.effective_staging_installables()?
                } else {
                    spec.staging_installables()?
                };
                std::iter::once(essentials)
                    .chain(other)
                    .for_each(|group| println!("{}", group.iter().join(" ")));
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(about = "Inspect or update spec metadata")]
    /// CLI command: get or set spec metadata entries.
    pub struct SpecMeta {
        #[command(subcommand)]
        cmd: SpecMetaCommands,
    }
    impl<C: Config> Command<C> for SpecMeta {
        fn exec(&self, conf: &C) -> Result<()> {
            match &self.cmd {
                SpecMetaCommands::Get(cmd) => cmd.exec(conf),
                SpecMetaCommands::Set(cmd) => cmd.exec(conf),
            }
        }
    }

    #[derive(Parser)]
    pub enum SpecMetaCommands {
        #[command(name = "get", alias = "get-meta")]
        Get(SpecGetMeta),
        #[command(name = "set", alias = "set-meta")]
        Set(SpecSetMeta),
    }

    #[derive(Parser)]
    #[command(about = "List spec names")]
    /// CLI command: list available spec names.
    pub struct SpecList;
    impl<C: Config> Command<C> for SpecList {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let (mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                for spec in mf.specs() {
                    println!("{}", spec.display_name());
                }
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(about = "List packages for a spec")]
    /// CLI command: list packages for a spec.
    pub struct SpecPackages {
        #[command(flatten)]
        inner: List,
    }
    impl<C: Config> Command<C> for SpecPackages {
        fn exec(&self, conf: &C) -> Result<()> {
            self.inner.exec(conf)
        }
    }

    #[derive(Parser)]
    #[command(about = "Add package requirements to a spec")]
    /// CLI command: add package requirements to a spec.
    pub struct SpecRequire {
        #[command(flatten)]
        inner: Require,
    }
    impl<C: Config> Command<C> for SpecRequire {
        fn exec(&self, conf: &C) -> Result<()> {
            self.inner.exec(conf)
        }
    }

    #[derive(Parser)]
    #[command(about = "Add package constraints to a spec")]
    /// CLI command: add package constraints to a spec.
    pub struct SpecForbid {
        #[command(flatten)]
        inner: Forbid,
    }
    impl<C: Config> Command<C> for SpecForbid {
        fn exec(&self, conf: &C) -> Result<()> {
            self.inner.exec(conf)
        }
    }

    #[derive(Parser)]
    #[command(about = "Remove requirements or constraints from a spec")]
    /// CLI command: remove requirements or constraints from a spec.
    pub struct SpecRemove {
        #[command(flatten)]
        inner: Remove,
    }
    impl<C: Config> Command<C> for SpecRemove {
        fn exec(&self, conf: &C) -> Result<()> {
            self.inner.exec(conf)
        }
    }

    #[derive(Parser)]
    #[command(about = "Manage spec artifacts")]
    /// CLI command: manage spec artifact staging.
    pub struct SpecArtifact {
        #[command(subcommand)]
        cmd: SpecArtifactCommands,
    }
    impl<C: Config> Command<C> for SpecArtifact {
        fn exec(&self, conf: &C) -> Result<()> {
            match &self.cmd {
                SpecArtifactCommands::Add(cmd) => cmd.exec(conf),
                SpecArtifactCommands::Remove(cmd) => cmd.exec(conf),
            }
        }
    }

    #[derive(Parser)]
    pub enum SpecArtifactCommands {
        #[command(name = "add", alias = "stage")]
        Add(SpecArtifactAdd),
        #[command(name = "remove", alias = "unstage")]
        Remove(SpecArtifactRemove),
    }

    #[derive(Parser)]
    #[command(about = "Stage an artifact into a spec")]
    /// CLI command: stage an artifact into a spec.
    pub struct SpecArtifactAdd {
        #[command(flatten)]
        inner: Stage,
    }
    impl<C: Config> Command<C> for SpecArtifactAdd {
        fn exec(&self, conf: &C) -> Result<()> {
            self.inner.exec(conf)
        }
    }

    #[derive(Parser)]
    #[command(about = "Remove a staged artifact from a spec")]
    /// CLI command: unstage an artifact from a spec.
    pub struct SpecArtifactRemove {
        #[command(flatten)]
        inner: Unstage,
    }
    impl<C: Config> Command<C> for SpecArtifactRemove {
        fn exec(&self, conf: &C) -> Result<()> {
            self.inner.exec(conf)
        }
    }

    #[derive(Parser)]
    #[command(about = "Get a spec meta value")]
    /// CLI command: get a spec meta entry.
    pub struct SpecGetMeta {
        /// Spec name (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,
        /// Meta name
        #[arg(value_name = "NAME")]
        name: String,
    }

    impl<C: Config> Command<C> for SpecGetMeta {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let (mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                let spec = mf.lookup_spec(self.spec.as_deref())?;
                match spec.get_meta(&self.name)? {
                    Some(value) => {
                        println!("{}", value);
                        Ok(())
                    }
                    None => Err(anyhow!(
                        "meta {} not found in spec {}",
                        self.name,
                        spec.display_name()
                    )),
                }
            })
        }
    }

    #[derive(Parser)]
    #[command(about = "Set a spec meta value")]
    /// CLI command: set a spec meta entry.
    pub struct SpecSetMeta {
        /// Spec name (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,
        /// Meta name
        #[arg(value_name = "NAME")]
        name: String,
        /// Meta value
        #[arg(value_name = "VALUE")]
        value: String,
    }

    impl<C: Config> Command<C> for SpecSetMeta {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let (mut mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                mf.set_spec_meta(self.spec.as_deref(), &self.name, &self.value)?;
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Set, modify or clear a spec's parents",
        long_about = "Manage the `extends` list of a spec.\n\
            \n\
            With one or more positional PARENT arguments, the entire parent set is \
            replaced.  Use --add to append, --remove to drop a single parent without \
            otherwise touching the list, and --clear to remove the relationship \
            entirely.  Multi-parent inheritance is strictly additive: a child spec \
            cannot remove packages, build-env entries, meta entries, or stage \
            artifacts contributed by any ancestor."
    )]
    /// CLI command: set, modify, or clear the extends list of a spec.
    pub struct SpecExtend {
        /// Spec name (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,
        /// Remove the extends relationship entirely
        #[arg(long = "clear", conflicts_with_all = ["parents", "add", "remove"])]
        clear: bool,
        /// Append PARENT to the existing parent list (repeatable)
        #[arg(long = "add", value_name = "PARENT", conflicts_with_all = ["parents", "remove"])]
        add: Vec<String>,
        /// Remove PARENT from the existing parent list (repeatable)
        #[arg(long = "remove", value_name = "PARENT", conflicts_with_all = ["parents", "add"])]
        remove: Vec<String>,
        /// Parent spec name(s) -- replaces the existing parent list
        #[arg(value_name = "PARENT")]
        parents: Vec<String>,
    }

    impl<C: Config> Command<C> for SpecExtend {
        fn exec(&self, conf: &C) -> Result<()> {
            if self.parents.is_empty()
                && self.add.is_empty()
                && self.remove.is_empty()
                && !self.clear
            {
                return Err(anyhow!(
                    "specify one or more PARENT arguments, --add, --remove, or --clear"
                ));
            }
            smol::block_on(async move {
                let (mut mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                let new_parents: Vec<String> = if self.clear {
                    Vec::new()
                } else if !self.add.is_empty() || !self.remove.is_empty() {
                    // Start from the spec's current parent list and apply
                    // add/remove deltas.  Either set may be empty, but the
                    // top-level guard above ensures at least one is given.
                    let current = mf
                        .lookup_spec(self.spec.as_deref())
                        .ok()
                        .map(|spec| {
                            spec.parents()
                                .map(|parents| {
                                    parents
                                        .into_iter()
                                        .map(|p| {
                                            p.name().map(|n| n.to_string()).unwrap_or_default()
                                        })
                                        .collect::<Vec<_>>()
                                })
                                .unwrap_or_default()
                        })
                        .unwrap_or_default();
                    let mut next: Vec<String> =
                        current.into_iter().filter(|p| !p.is_empty()).collect();
                    for parent in &self.remove {
                        match next.iter().position(|p| p == parent) {
                            Some(idx) => {
                                next.remove(idx);
                            }
                            None => {
                                return Err(anyhow!("spec does not currently extend '{}'", parent));
                            }
                        }
                    }
                    for parent in &self.add {
                        if next.iter().any(|p| p == parent) {
                            return Err(anyhow!("'{}' is already a parent", parent));
                        }
                        next.push(parent.clone());
                    }
                    next
                } else {
                    self.parents.clone()
                };
                let parent_refs: Vec<&str> = new_parents.iter().map(String::as_str).collect();
                mf.set_extends(self.spec.as_deref(), &parent_refs)?;
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                mf.resolve(conf.concurrency(), fetcher).await?;
                mf.store().await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        name = "spec-hash",
        about = "Show a spec hash",
        long_about = "Print the hash of a spec definition. By default the hash is printed as a hexadecimal string; use --sri to emit Subresource Integrity format."
    )]
    /// CLI command: show hash for a spec entry.
    pub struct SpecHash {
        /// Use SRI format for the hash output
        ///
        /// Outputs the hash as SRI (Subresource Integrity) format, e.g.
        /// `sha256-<base64-encoded-hash>`. If not specified, the hash is printed
        /// as a hexadecimal string.
        #[arg(long = "sri", action)]
        sri: bool,
        /// Spec name (omit to use the default spec)
        #[arg(value_name = "SPEC")]
        spec: Option<String>,
    }

    impl<C: Config> Command<C> for SpecHash {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let (mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                let hash = mf.lookup_spec(self.spec.as_deref())?.locked_hash()?;
                if self.sri {
                    println!("{}", hash.to_sri());
                } else {
                    println!("{}", hash.to_hex());
                }
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Show a package's control record",
        long_about = "Print the raw control record for the given package."
    )]
    /// CLI command: show metadata for a package.
    pub struct PackageShow {
        /// Package name
        #[arg(value_name = "PACKAGE")]
        package: String,
    }

    impl<C: Config> Command<C> for PackageShow {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                mf.load_universe(conf.concurrency(), fetcher).await?;
                guard.commit().await?;
                let pkg = mf.universe_packages()?.find(|p| self.package == p.name());
                if let Some(pkg) = pkg {
                    std::io::stdout().lock().write_all(pkg.src().as_bytes())?;
                    Ok(())
                } else {
                    Err(anyhow!("package {} not found", &self.package))
                }
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Show a package's source control record",
        long_about = "Print the raw source control record for the given package or source package."
    )]
    /// CLI command: show metadata for a source package.
    pub struct SourceShow {
        /// Find and print source files as artifacts to stage in path.
        /// Fails if there are multiple source packages matching the name.
        #[arg(long = "stage-to", value_name = "PATH")]
        stage_to: Option<String>,
        /// Package or source package name. May include version (pkg=version)
        #[arg(value_name = "PACKAGE")]
        package: String,
    }

    impl<C: Config> Command<C> for SourceShow {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let name = ProvidedName::try_parse_display(&self.package).map_err(|err| {
                    anyhow!("invalid package/source name '{}': {}", &self.package, err)
                })?;
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, _) = Manifest::from_file(conf.manifest(), conf.arch()).await?;
                mf.load_source_universe(conf.concurrency(), fetcher).await?;
                guard.commit().await?;
                let found = mf.find_source(&name)?;
                if found.is_empty() {
                    return Err(anyhow!("package/source {} not found", &self.package));
                }
                let mut out = std::io::stdout().lock();
                if let Some(target) = self.stage_to.as_deref() {
                    if found.len() > 1 {
                        return Err(anyhow!(
                            "multiple source packages found for {}, cannot show as artifacts",
                            &self.package
                        ));
                    }
                    let src = found.first().unwrap();
                    let mut first = true;
                    let mut staged = vec![];
                    for artifact in src.files() {
                        if first {
                            first = false;
                        } else {
                            out.write_all(b"\n")?;
                        }
                        let file_name = match artifact.path.rsplit('/').next() {
                            Some(name) => name,
                            None => &artifact.path,
                        };
                        out.write_all(
                            format!(
                                "[artifact.\"{}\"]
type = \"file\"
target = \"{}/{}\"
size = {}
hash = \"{}\"
",
                                &artifact.path,
                                target.trim_end_matches('/'),
                                file_name,
                                artifact.size,
                                artifact.hash.to_sri(),
                            )
                            .as_bytes(),
                        )?;
                        if is_comp_ext(&artifact.path) {
                            out.write_all(b"unpack = false\n")?;
                        }
                        staged.push(&artifact.path);
                    }
                    out.write_all(b"\nstage = [\n")?;
                    for staged in staged {
                        out.write_all(format!("    \"{}\",\n", staged).as_bytes())?;
                    }
                    out.write_all(b"]\n")?;
                } else {
                    for src in found.iter() {
                        out.write_all(src.as_ref().as_bytes())?;
                        out.write_all(b"\n")?;
                    }
                }
                out.flush()?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(name = "package", about = "Inspect packages")]
    /// CLI command: inspect package records.
    pub struct PackageCmd {
        #[command(subcommand)]
        cmd: PackageCommands,
    }
    impl<C: Config> Command<C> for PackageCmd {
        fn exec(&self, conf: &C) -> Result<()> {
            match &self.cmd {
                PackageCommands::Show(cmd) => cmd.exec(conf),
                PackageCommands::Search(cmd) => cmd.exec(conf),
            }
        }
    }

    #[derive(Parser)]
    pub enum PackageCommands {
        Show(PackageShow),
        Search(PackageSearch),
    }

    #[derive(Parser)]
    #[command(about = "Search for packages in the universe")]
    /// CLI command: search for packages in the universe.
    pub struct PackageSearch {
        #[command(flatten)]
        inner: Search,
    }
    impl<C: Config> Command<C> for PackageSearch {
        fn exec(&self, conf: &C) -> Result<()> {
            self.inner.exec(conf)
        }
    }

    #[derive(Parser)]
    #[command(name = "source", about = "Inspect source packages")]
    /// CLI command: inspect source package records.
    pub struct SourceCmd {
        #[command(subcommand)]
        cmd: SourceCommands,
    }
    impl<C: Config> Command<C> for SourceCmd {
        fn exec(&self, conf: &C) -> Result<()> {
            match &self.cmd {
                SourceCommands::Show(cmd) => cmd.exec(conf),
            }
        }
    }

    #[derive(Parser)]
    pub enum SourceCommands {
        Show(SourceShow),
    }

    #[derive(Parser)]
    #[command(name = "archive", about = "Manage archives")]
    /// CLI command: manage archive entries.
    pub struct ArchiveCmd {
        #[command(subcommand)]
        cmd: ArchiveCommands,
    }
    impl<C: Config> Command<C> for ArchiveCmd {
        fn exec(&self, conf: &C) -> Result<()> {
            match &self.cmd {
                ArchiveCommands::Add(cmd) => cmd.exec(conf),
                ArchiveCommands::Remove(cmd) => cmd.exec(conf),
            }
        }
    }

    #[derive(Parser)]
    pub enum ArchiveCommands {
        Add(ArchiveAdd),
        Remove(ArchiveRemove),
    }

    #[derive(Parser)]
    #[command(name = "artifact", about = "Manage artifacts")]
    /// CLI command: manage artifact entries.
    pub struct ArtifactCmd {
        #[command(subcommand)]
        cmd: ArtifactCommands,
    }
    impl<C: Config> Command<C> for ArtifactCmd {
        fn exec(&self, conf: &C) -> Result<()> {
            match &self.cmd {
                ArtifactCommands::Add(cmd) => cmd.exec(conf),
            }
        }
    }

    #[derive(Parser)]
    pub enum ArtifactCommands {
        Add(ArtifactAdd),
    }

    #[derive(Parser)]
    #[command(name = "deb", about = "Manage local .deb files")]
    /// CLI command: manage local package entries.
    pub struct DebCmd {
        #[command(subcommand)]
        cmd: DebCommands,
    }
    impl<C: Config> Command<C> for DebCmd {
        fn exec(&self, conf: &C) -> Result<()> {
            match &self.cmd {
                DebCommands::Add(cmd) => cmd.exec(conf),
                DebCommands::Remove(cmd) => cmd.exec(conf),
            }
        }
    }

    #[derive(Parser)]
    pub enum DebCommands {
        Add(DebAdd),
        Remove(DebRemove),
    }

    fn normalize_hash_name(name: &str) -> Result<&'static str> {
        let name = name.trim();
        if name.is_empty() {
            return Err(anyhow!("hash name cannot be empty"));
        }
        match name.to_ascii_lowercase().as_str() {
            "md5" | "md5sum" => Ok(md5::Md5::NAME),
            "sha1" => Ok(sha1::Sha1::NAME),
            "sha256" => Ok(sha2::Sha256::NAME),
            "sha512" => Ok(sha2::Sha512::NAME),
            "blake3" => Ok(blake3::Hasher::NAME),
            _ => Err(anyhow!(
                "unsupported hash {}, expected one of: md5, sha1, sha256, sha512, blake3",
                name
            )),
        }
    }

    #[derive(Parser)]
    pub enum ToolCommands {
        HexToSri(ToolHexToSri),
        SriToHex(ToolSriToHex),
        Hash(ToolHash),
    }

    #[derive(Parser)]
    #[command(about = "Internal tooling helpers", hide = true)]
    /// CLI command: tool entrypoint for helpers.
    pub struct Tool {
        #[command(subcommand)]
        cmd: ToolCommands,
    }

    impl<C: Config> Command<C> for Tool {
        fn exec(&self, conf: &C) -> Result<()> {
            match &self.cmd {
                ToolCommands::HexToSri(cmd) => cmd.exec(conf),
                ToolCommands::SriToHex(cmd) => cmd.exec(conf),
                ToolCommands::Hash(cmd) => cmd.exec(conf),
            }
        }
    }

    #[derive(Parser)]
    #[command(
        name = "hex-to-sri",
        about = "Convert a hex digest to SRI format",
        long_about = "Convert a hex digest and hash name to SRI (Subresource Integrity) format."
    )]
    /// CLI tool: convert hex hash to SRI format.
    pub struct ToolHexToSri {
        #[arg(value_name = "HASH_NAME")]
        hash: String,
        #[arg(value_name = "HEX_DIGEST")]
        digest: String,
    }

    impl<C: Config> Command<C> for ToolHexToSri {
        fn exec(&self, _conf: &C) -> Result<()> {
            let name = normalize_hash_name(&self.hash)?;
            let hash = Hash::from_hex(name, &self.digest)
                .map_err(|err| anyhow!("error decoding hex digest: {}", err))?;
            println!("{}", hash.to_sri());
            Ok(())
        }
    }

    #[derive(Parser)]
    #[command(
        name = "sri-to-hex",
        about = "Convert an SRI digest to hex",
        long_about = "Convert an SRI (Subresource Integrity) digest to hexadecimal."
    )]
    /// CLI tool: convert SRI hash to hex format.
    pub struct ToolSriToHex {
        #[arg(value_name = "SRI_DIGEST")]
        digest: String,
    }

    impl<C: Config> Command<C> for ToolSriToHex {
        fn exec(&self, _conf: &C) -> Result<()> {
            let hash = Hash::from_sri(&self.digest)
                .map_err(|err| anyhow!("error decoding SRI digest: {}", err))?;
            println!("{}", hash.to_hex());
            Ok(())
        }
    }

    #[derive(Parser)]
    #[command(
        name = "hash",
        about = "Hash a file, directory, or stdin",
        long_about = "Hash a file, directory, or stdin. For directories the artifact tree hash (blake3) is used."
    )]
    /// CLI tool: compute file hashes.
    pub struct ToolHash {
        #[arg(value_name = "HASH_NAME")]
        hash: String,
        /// Output in SRI format instead of hex
        #[arg(long = "sri", conflicts_with = "hex", action)]
        sri: bool,
        /// Output in hex (default)
        #[arg(long = "hex", conflicts_with = "sri", action)]
        hex: bool,
        /// File or directory to hash (stdin if omitted)
        #[arg(value_name = "PATH")]
        path: Option<PathBuf>,
    }

    impl<C: Config> Command<C> for ToolHash {
        fn exec(&self, _conf: &C) -> Result<()> {
            let name = normalize_hash_name(&self.hash)?;
            let use_sri = self.sri;
            smol::block_on(async move {
                let hash = if let Some(path) = self.path.as_ref() {
                    let meta = smol::fs::metadata(path)
                        .await
                        .map_err(|err| anyhow!("failed to stat {}: {}", path.display(), err))?;
                    if meta.is_dir() {
                        let (hash, _) = hash_directory(path, name).await.map_err(|err| {
                            anyhow!("failed to hash directory {}: {}", path.display(), err)
                        })?;
                        hash
                    } else {
                        let file = smol::fs::File::open(path)
                            .await
                            .map_err(|err| anyhow!("failed to open {}: {}", path.display(), err))?;
                        let mut hasher = Hash::hashing_reader_for(name, file)?;
                        smol::io::copy(&mut hasher, &mut smol::io::sink())
                            .await
                            .map_err(|err| anyhow!("failed to read stdin: {}", err))?;
                        hasher.as_mut().hash()
                    }
                } else {
                    let stdin = async_io::Async::new(std::io::stdin())
                        .map_err(|err| anyhow!("failed to read stdin: {}", err))?;
                    let mut hasher = Hash::hashing_reader_for(name, stdin)?;
                    smol::io::copy(&mut hasher, &mut smol::io::sink())
                        .await
                        .map_err(|err| anyhow!("failed to read stdin: {}", err))?;
                    hasher.as_mut().hash()
                };
                if use_sri {
                    println!("{}", hash.to_sri());
                } else {
                    println!("{}", hash.to_hex());
                }
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "List spec packages",
        long_about = "List spec packages. Use -e to see only essential packages, or -s to specify a non-default spec."
    )]
    /// CLI command: list manifest entries.
    pub struct List {
        #[arg(short = 'e', long = "only-essential", hide = true)]
        only_essential: bool,
        /// List packages for the target spec (omit to use the default spec)
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,
        /// Flat the spec hierarchy and list all effective packages, including inherited
        #[arg(long = "flat", action)]
        flat: bool,
    }

    impl<C: Config> Command<C> for List {
        fn exec(&self, conf: &C) -> Result<()> {
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let (mut mf, has_valid_lock) =
                    Manifest::from_file(conf.manifest(), conf.arch()).await?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                mf.load_universe(conf.concurrency(), fetcher).await?;
                guard.commit().await?;
                let spec = mf.lookup_spec(self.spec.as_deref())?;
                let mut pkgs = if self.flat {
                    spec.effective_packages()?
                } else {
                    spec.packages()?
                };
                pkgs.sort_by_key(|&pkg| pkg.name());
                let out = pretty_print_packages(pkgs, false)?;
                std::io::stdout().lock().write_all(&out)?;
                Ok(())
            })
        }
    }

    #[derive(Parser)]
    #[command(
        about = "Build an installable root for a spec",
        long_about = "Build an installable root filesystem tree for a spec."
    )]
    /// CLI command: build repository artifacts.
    pub struct Build {
        /// The spec name to build
        #[arg(short = 's', long = "spec", value_name = "SPEC")]
        spec: Option<String>,
        /// Build executor to use
        #[arg(
            long = "executor",
            value_name = "EXECUTOR",
            value_enum,
            default_value = "sandbox"
        )]
        executor: ExecutorKind,
        /// The target directory
        #[arg(short, long, value_name = "PATH")]
        path: PathBuf,
    }

    impl<C: Config<FS = HostFileSystem, Cache = HostCache>> Command<C> for Build {
        fn exec(&self, conf: &C) -> Result<()> {
            let mut builder = self.executor.build_executor(&self.path)?;
            tracing::debug!("current process state {}", current_process_state(),);
            smol::block_on(async move {
                let fetcher = conf.fetcher()?;
                let guard = fetcher.init().await?;
                let fs = HostFileSystem::new(&self.path, rustix::process::geteuid().is_root())
                    .await
                    .map_err(|err| {
                        anyhow!(
                            "failed to initialize staging filesystem at {}: {}",
                            self.path.display(),
                            err
                        )
                    })?;
                let (manifest, has_valid_lock) = Manifest::from_file(conf.manifest(), conf.arch())
                    .await
                    .map_err(|err| {
                        anyhow!(
                            "failed to load manifest from {}: {}",
                            conf.manifest().display(),
                            err
                        )
                    })?;
                if !has_valid_lock {
                    return Err(anyhow!("manifest lock is not live; run update first"));
                }
                let pb = if conf.log_level() == 0 {
                    let use_tty = std::io::stderr().is_terminal();
                    Some(move |size| {
                        if use_tty {
                            StageProgress::from_progress_bar(
                                ProgressBar::new(size).with_style(
                                    indicatif::ProgressStyle::with_template(
                                        "staging files: {spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta}) {msg}",
                                    )
                                    .unwrap()
                                    .progress_chars("#>-"),
                                ).with_finish(indicatif::ProgressFinish::AndClear),
                            )
                        } else {
                            StageProgress::percent(size)
                        }
                    })
                } else {
                    None
                };
                tracing::debug!(
                    "Staging spec '{}' into '{}'",
                    self.spec.as_deref().unwrap_or("default"),
                    self.path.display()
                );
                let (essentials, other, scripts, build_env) = manifest
                    .stage(
                        self.spec.as_deref(),
                        None,
                        &fs,
                        conf.concurrency(),
                        fetcher,
                        pb,
                    )
                    .await?;
                builder
                    .build(&fs, essentials, other, scripts, build_env)
                    .await?;
                guard.commit().await?;
                Ok(())
            })
        }
    }

    fn manifest_dir_for(manifest: &Path, cwd: &Path) -> PathBuf {
        let manifest = if manifest.is_absolute() {
            manifest.to_path_buf()
        } else {
            cwd.join(manifest)
        };
        let manifest = normalize_path(&manifest);
        manifest
            .parent()
            .filter(|path| !path.as_os_str().is_empty())
            .unwrap_or_else(|| Path::new("."))
            .to_path_buf()
    }

    fn normalize_path(path: &Path) -> PathBuf {
        let mut out = PathBuf::new();
        let mut normal_parts: Vec<OsString> = Vec::new();

        for component in path.components() {
            match component {
                Component::Prefix(prefix) => out.push(prefix.as_os_str()),
                Component::RootDir => out.push(component.as_os_str()),
                Component::CurDir => {}
                Component::ParentDir => {
                    if !normal_parts.is_empty() {
                        normal_parts.pop();
                    } else if out.as_os_str().is_empty() {
                        normal_parts.push(component.as_os_str().to_os_string());
                    }
                }
                Component::Normal(part) => normal_parts.push(part.to_os_string()),
            }
        }

        for part in normal_parts {
            out.push(part);
        }

        if out.as_os_str().is_empty() {
            PathBuf::from(".")
        } else {
            out
        }
    }

    fn diff_relative_path(path: &Path, base: &Path) -> Option<PathBuf> {
        let path = normalize_path(path);
        let base = normalize_path(base);
        let path_components = path.components().collect::<Vec<_>>();
        let base_components = base.components().collect::<Vec<_>>();

        if path_components
            .first()
            .zip(base_components.first())
            .map(|(lhs, rhs)| lhs != rhs)
            .unwrap_or(false)
        {
            return None;
        }

        let common_len = path_components
            .iter()
            .zip(base_components.iter())
            .take_while(|(lhs, rhs)| lhs == rhs)
            .count();

        let mut relative = PathBuf::new();
        for component in &base_components[common_len..] {
            match component {
                Component::Normal(_) | Component::ParentDir => relative.push(".."),
                Component::CurDir => {}
                Component::RootDir | Component::Prefix(_) => return None,
            }
        }
        for component in &path_components[common_len..] {
            relative.push(component.as_os_str());
        }

        if relative.as_os_str().is_empty() {
            Some(PathBuf::from("."))
        } else {
            Some(relative)
        }
    }

    fn rebase_local_path_for_manifest(path: &Path, manifest: &Path, cwd: &Path) -> PathBuf {
        if path.is_absolute() {
            return path.to_path_buf();
        }
        let cwd = normalize_path(cwd);
        let manifest_dir = manifest_dir_for(manifest, &cwd);
        if manifest_dir == cwd {
            return path.to_path_buf();
        }
        let resolved = normalize_path(&cwd.join(path));
        diff_relative_path(&resolved, &manifest_dir).unwrap_or(resolved)
    }

    fn rebase_local_input_for_manifest(path: &str, manifest: &Path, cwd: &Path) -> String {
        rebase_local_path_for_manifest(Path::new(path), manifest, cwd)
            .to_string_lossy()
            .into_owned()
    }

    fn rebase_cli_local_input(path: &str, manifest: &Path) -> io::Result<String> {
        let cwd = std::env::current_dir()?;
        Ok(rebase_local_input_for_manifest(path, manifest, &cwd))
    }

    fn rebase_pathbuf_input(path: &Path, manifest: &Path) -> io::Result<PathBuf> {
        if path.is_absolute() {
            Ok(path.to_path_buf())
        } else {
            Ok(PathBuf::from(rebase_cli_local_input(
                &path.to_string_lossy(),
                manifest,
            )?))
        }
    }

    fn rebase_signed_by_input(
        signed_by: &Option<SignedBy>,
        manifest: &Path,
    ) -> io::Result<Option<SignedBy>> {
        match signed_by {
            Some(SignedBy::Keyring(path)) => Ok(Some(SignedBy::Keyring(rebase_pathbuf_input(
                path, manifest,
            )?))),
            Some(SignedBy::Builtin) => Ok(Some(SignedBy::Builtin)),
            Some(SignedBy::Key(key)) => Ok(Some(SignedBy::Key(key.clone()))),
            None => Ok(None),
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;
        use std::path::{Path, PathBuf};

        #[test]
        fn normalize_path_handles_curdir_parentdir_and_empty_collapse() {
            // CurDir components are stripped
            assert_eq!(
                normalize_path(Path::new("./foo/./bar")),
                PathBuf::from("foo/bar")
            );

            // ParentDir pops normal components
            assert_eq!(
                normalize_path(Path::new("foo/../bar")),
                PathBuf::from("bar")
            );

            // Multiple ParentDir
            assert_eq!(
                normalize_path(Path::new("foo/bar/../../baz")),
                PathBuf::from("baz")
            );

            // Collapses to "."
            assert_eq!(normalize_path(Path::new("foo/..")), PathBuf::from("."));

            // CurDir alone
            assert_eq!(normalize_path(Path::new(".")), PathBuf::from("."));

            // ParentDir with nothing to pop goes through
            assert_eq!(normalize_path(Path::new("../foo")), PathBuf::from("../foo"));
        }

        #[test]
        fn diff_relative_path_edge_cases() {
            // Same path → "."
            assert_eq!(
                diff_relative_path(Path::new("foo/bar"), Path::new("foo/bar")),
                Some(PathBuf::from("."))
            );

            // Divergent first components (relative vs absolute)
            assert_eq!(
                diff_relative_path(Path::new("/abs/path"), Path::new("rel/path")),
                None,
            );
        }

        #[test]
        fn rebase_local_path_for_manifest_short_circuits_absolute() {
            let abs = Path::new("/absolute/path/file.txt");
            let manifest = Path::new("/some/manifest.toml");
            let cwd = Path::new("/working");
            let result = rebase_local_path_for_manifest(abs, manifest, cwd);
            assert_eq!(result, PathBuf::from("/absolute/path/file.txt"));
        }

        #[test]
        fn rebase_signed_by_covers_builtin_and_key() {
            let manifest = Path::new("/tmp/manifest.toml");

            let builtin =
                rebase_signed_by_input(&Some(SignedBy::Builtin), manifest).expect("builtin");
            assert_eq!(builtin, Some(SignedBy::Builtin));

            let key = rebase_signed_by_input(
                &Some(SignedBy::Key("-----BEGIN PGP PUBLIC KEY BLOCK-----".into())),
                manifest,
            )
            .expect("key");
            assert_eq!(
                key,
                Some(SignedBy::Key("-----BEGIN PGP PUBLIC KEY BLOCK-----".into()))
            );

            let none = rebase_signed_by_input(&None, manifest).expect("none");
            assert_eq!(none, None);
        }

        #[test]
        fn manifest_dir_for_joins_cwd_for_relative_manifest() {
            // relative manifest is joined with cwd
            let dir = manifest_dir_for(Path::new("manifest.toml"), Path::new("/cwd"));
            assert_eq!(dir, PathBuf::from("/cwd"));

            // absolute manifest ignores cwd
            let dir = manifest_dir_for(Path::new("/etc/manifest.toml"), Path::new("/cwd"));
            assert_eq!(dir, PathBuf::from("/etc"));
        }
    }
}

/// A parser type for converting command-line argument strings into a `Dependency`.
///
/// Example:
/// ```ignore
/// #[derive(clap::Parser)]
/// struct Args {
///     #[arg(value_parser = DependencyParser)]
///     dependency: Vec<Dependency>,
/// }
/// ```
#[derive(Clone)]
/// Parser for dependency strings in CLI arguments.
pub struct DependencyParser;

impl clap::builder::TypedValueParser for DependencyParser {
    type Value = Dependency<String>;

    fn parse_ref(
        &self,
        cmd: &clap::Command,
        arg: Option<&clap::Arg>,
        value: &std::ffi::OsStr,
    ) -> Result<Self::Value, clap::Error> {
        let value = value.to_str().ok_or_else(|| {
            let mut err = clap::Error::new(clap::error::ErrorKind::InvalidUtf8).with_cmd(cmd);
            if let Some(arg) = arg {
                err.insert(
                    clap::error::ContextKind::InvalidArg,
                    clap::error::ContextValue::String(arg.to_string()),
                );
            }
            err
        })?;
        Self::Value::from_str(value).map_err(|e| {
            let mut err = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
            if let Some(arg) = arg {
                err.insert(
                    clap::error::ContextKind::InvalidArg,
                    clap::error::ContextValue::String(arg.to_string()),
                );
            }
            err.insert(
                clap::error::ContextKind::InvalidValue,
                clap::error::ContextValue::String(value.to_string()),
            );
            err.insert(
                clap::error::ContextKind::Custom,
                clap::error::ContextValue::String(format!("{}", e)),
            );
            err
        })
    }
}

struct PackageDisplay<'a> {
    name: &'a str,
    arch: &'a str,
    ver: Version<&'a str>,
    desc: &'a str,
    prio: InstallPriority,
}

impl<'a> From<&'a Package<'a>> for PackageDisplay<'a> {
    fn from(pkg: &'a Package<'a>) -> Self {
        Self {
            name: pkg.name(),
            arch: pkg.arch(),
            ver: pkg.raw_version(),
            desc: pkg
                .field("Description")
                .map_or("", |d| d.split('\n').next().unwrap_or("")),
            prio: pkg.install_priority(),
        }
    }
}

pub fn pretty_print_packages<'a>(
    iter: impl IntoIterator<Item = &'a Package<'a>>,
    sort: bool,
) -> Result<Vec<u8>> {
    let mut w0 = 0usize;
    let mut w1 = 0usize;
    let mut w2 = 0usize;
    let mut w3 = 0usize;
    let mut w4 = 0usize;
    let mut packages = iter
        .into_iter()
        .map(PackageDisplay::try_from)
        .map(|pkg| {
            let pkg = pkg?;
            w0 = std::cmp::max(w0, pkg.arch.len());
            w1 = std::cmp::max(w1, pkg.prio.as_ref().len());
            w2 = std::cmp::max(w2, pkg.name.len());
            w3 = std::cmp::max(w3, pkg.ver.as_ref().len());
            w4 = std::cmp::max(w4, pkg.desc.len());
            Ok(pkg)
        })
        .collect::<Result<Vec<_>>>()?;
    if sort {
        packages.sort_by(|this, that| match this.name.cmp(that.name) {
            std::cmp::Ordering::Equal => this.ver.cmp(&that.ver),
            other => other,
        });
    }
    let mut buf = Vec::new();
    for p in packages.iter() {
        use std::io::Write;
        writeln!(
            &mut buf,
            "{:>w0$} {:<w2$} {:>w3$} {:<w4$}",
            p.arch, p.name, p.ver, p.desc
        )?;
    }
    Ok(buf)
}

/// A parser type for converting command-line argument strings into a `Constraint`.
///
/// Example:
/// ```ignore
/// #[derive(clap::Parser)]
/// struct Args {
///     #[arg(value_parser = ConstraintParser)]
///     dependency: Vec<Constraint>,
/// }
/// ```
#[derive(Clone)]
/// Parser for version constraints in CLI arguments.
pub struct ConstraintParser;

impl clap::builder::TypedValueParser for ConstraintParser {
    type Value = Constraint<String>;

    fn parse_ref(
        &self,
        cmd: &clap::Command,
        arg: Option<&clap::Arg>,
        value: &std::ffi::OsStr,
    ) -> Result<Self::Value, clap::Error> {
        let value = value.to_str().ok_or_else(|| {
            let mut err = clap::Error::new(clap::error::ErrorKind::InvalidUtf8).with_cmd(cmd);
            if let Some(arg) = arg {
                err.insert(
                    clap::error::ContextKind::InvalidArg,
                    clap::error::ContextValue::String(arg.to_string()),
                );
            }
            err
        })?;
        Self::Value::from_str(value).map_err(|e| {
            let mut err = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
            if let Some(arg) = arg {
                err.insert(
                    clap::error::ContextKind::InvalidArg,
                    clap::error::ContextValue::String(arg.to_string()),
                );
            }
            err.insert(
                clap::error::ContextKind::InvalidValue,
                clap::error::ContextValue::String(value.to_string()),
            );
            err.insert(
                clap::error::ContextKind::Custom,
                clap::error::ContextValue::String(format!("{}", e)),
            );
            err
        })
    }
}

pub fn current_process_state() -> impl std::fmt::Display {
    ProcessState::collect()
}

struct ProcessState {
    uid: u32,
    euid: u32,
    gid: u32,
    egid: u32,
    uid_map: String,
    gid_map: String,
    setgroups: String,
    status_filtered: String,
    chown_test_result: String,
}

impl ProcessState {
    fn read_file(path: &str) -> String {
        std::fs::read_to_string(path).unwrap_or_else(|e| format!("(failed to read {path}: {e})"))
    }

    fn collect() -> Self {
        use rustix::process::{getegid, geteuid, getgid, getuid};
        let uid = getuid().as_raw();
        let euid = geteuid().as_raw();
        let gid = getgid().as_raw();
        let egid = getegid().as_raw();

        let uid_map = Self::read_file("/proc/self/uid_map");
        let gid_map = Self::read_file("/proc/self/gid_map");
        let setgroups = Self::read_file("/proc/self/setgroups");

        let status = Self::read_file("/proc/self/status");
        let mut status_filtered = String::new();
        for line in status.lines() {
            if line.starts_with("Uid:")
                || line.starts_with("Gid:")
                || line.starts_with("Groups:")
                || line.starts_with("CapEff:")
                || line.starts_with("CapPrm:")
                || line.starts_with("CapBnd:")
                || line.starts_with("NoNewPrivs:")
                || line.starts_with("Seccomp:")
            {
                status_filtered.push_str(line);
                status_filtered.push('\n');
            }
        }

        // Controlled chown test in current directory
        let chown_test_result = match Self::chown_test() {
            Ok(msg) => msg,
            Err(e) => format!("chown test failed to run: {e}"),
        };

        Self {
            uid,
            euid,
            gid,
            egid,
            uid_map,
            gid_map,
            setgroups,
            status_filtered,
            chown_test_result,
        }
    }

    fn chown_test() -> io::Result<String> {
        use rustix::fd::OwnedFd;
        use rustix::fs::{fchown, openat, unlinkat, AtFlags, Mode, OFlags};
        use rustix::process::{Gid, Uid};

        let path = "rdebootstrap_chown_test";

        // remove if exists
        let _ = unlinkat(rustix::fs::CWD, path, AtFlags::empty());

        let fd: OwnedFd = openat(
            rustix::fs::CWD,
            path,
            OFlags::CREATE | OFlags::WRONLY,
            Mode::from_bits_truncate(0o644),
        )?;

        let result = match fchown(&fd, Some(Uid::from_raw(0)), Some(Gid::from_raw(42))) {
            Ok(()) => "fchown(fd, 0, 42) succeeded".to_string(),
            Err(e) => format!("fchown(fd, 0, 42) failed: {e}"),
        };

        drop(fd);
        let _ = unlinkat(rustix::fs::CWD, path, AtFlags::empty());

        Ok(result)
    }
}

impl std::fmt::Display for ProcessState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "== ids ==")?;
        writeln!(
            f,
            "uid={} euid={} gid={} egid={}",
            self.uid, self.euid, self.gid, self.egid
        )?;

        writeln!(f, "\n== /proc/self/uid_map ==")?;
        writeln!(f, "{}", self.uid_map)?;

        writeln!(f, "== /proc/self/gid_map ==")?;
        writeln!(f, "{}", self.gid_map)?;

        writeln!(f, "== /proc/self/setgroups ==")?;
        writeln!(f, "{}", self.setgroups)?;

        writeln!(f, "== filtered /proc/self/status ==")?;
        writeln!(f, "{}", self.status_filtered)?;

        writeln!(f, "== chown test ==")?;
        writeln!(f, "{}", self.chown_test_result)?;

        Ok(())
    }
}
