inkhaven 1.2.3

Inkhaven — TUI literary work editor for Typst books
use std::path::Path;

use crate::config::Config;
use crate::error::{Error, Result};
use crate::project::ProjectLayout;
use crate::store::Store;
use crate::store::hierarchy::Hierarchy;
use crate::store::node::NodeKind;

pub fn run(project: &Path, node_path: &str, yes: bool) -> Result<()> {
    let layout = ProjectLayout::new(project);
    layout.require_initialized()?;

    let cfg = Config::load(&layout.config_path())?;
    let store = Store::open(layout.clone(), &cfg)?;
    let h = Hierarchy::load(&store)?;

    let node = h
        .find_by_path(node_path)
        .ok_or_else(|| Error::Store(format!("node not found: `{node_path}`")))?;
    if node.protected {
        return Err(Error::Store(format!(
            "`{}` is a system book — it can't be deleted",
            node.title
        )));
    }
    if let Some(help_anc) = h.ancestors(node).into_iter().find(|a| {
        a.protected && a.system_tag.as_deref() == Some(crate::store::SYSTEM_TAG_HELP)
    }) {
        return Err(Error::Store(format!(
            "`{}` lives inside the read-only Help book (`{}`)",
            node.title, help_anc.title
        )));
    }
    let ids = h.collect_subtree(node.id);
    let descendant_count = ids.len().saturating_sub(1);

    if !yes {
        return Err(Error::Store(format!(
            "would delete `{}` ({}) and {} descendant(s); pass --yes to confirm",
            node_path,
            node.kind.as_str(),
            descendant_count
        )));
    }

    let fs_rel = match node.kind {
        NodeKind::Paragraph => node
            .file
            .as_ref()
            .map(std::path::PathBuf::from)
            .unwrap_or_default(),
        _ => h.fs_path(node, &layout),
    };

    store.delete_subtree(&fs_rel, &ids)?;

    eprintln!(
        "deleted {} `{}` ({} other node{} removed)",
        node.kind.as_str(),
        node.title,
        descendant_count,
        if descendant_count == 1 { "" } else { "s" }
    );
    Ok(())
}