use super::*;

/// Default `.mnem/config.toml` body written by `mnem init` so a fresh
/// `mnem retrieve "x"` succeeds out-of-the-box. Idempotent: if
/// `config.toml` already exists, init does NOT overwrite it.
///
/// previously `mnem init` created the repo
/// skeleton but never wrote `[embed]`, so a first-time retrieve
/// errored cryptically. Seeding sensible defaults is the smallest
/// fix that turns the box-fresh path into a working demo.
///
/// under the `bundled-embedder` cargo feature
/// (Path A - `cargo install mnem-cli --features bundled-embedder`)
/// the seeded default flips to onnx + all-MiniLM-L6-v2. The model
/// downloads on first use and caches under
/// `~/.cache/huggingface/hub`; no Ollama install, no OpenAI key,
/// no manual `mnem config set embed.provider ...` step. Default
/// builds (no bundled-embedder) keep the legacy Ollama seed so
/// users with a local `ollama serve` keep working unchanged.
#[cfg(feature = "bundled-embedder")]
const DEFAULT_CONFIG_TOML: &str = "\
# Auto-generated by `mnem init`. Override with `mnem config set <key> <val>`
# or edit this file. See https://github.com/Uranid/mnem/blob/main/CONFIGURABLES.md
[embed]
provider = \"onnx\"
model = \"all-MiniLM-L6-v2\"
";

#[cfg(not(feature = "bundled-embedder"))]
const DEFAULT_CONFIG_TOML: &str = "\
# Auto-generated by `mnem init`. Override with `mnem config set <key> <val>`
# or edit this file. See https://github.com/Uranid/mnem/blob/main/CONFIGURABLES.md
[embed]
provider = \"ollama\"
model = \"nomic-embed-text\"
";

/// Human-readable provider tag shown in `mnem init`'s stdout hint so
/// the operator sees the exact provider their fresh repo will use.
#[cfg(feature = "bundled-embedder")]
const DEFAULT_PROVIDER_DESC: &str = "onnx:all-MiniLM-L6-v2";

#[cfg(not(feature = "bundled-embedder"))]
const DEFAULT_PROVIDER_DESC: &str = "ollama:nomic-embed-text";

#[derive(clap::Args, Debug)]
#[command(after_long_help = "\
Examples:
 mnem init # create .mnem/ in the current directory
 mnem init ~/notes # create ~/notes/.mnem/
 mnem -R ~/notes status # verify by reading op_id + head commit
")]
pub(crate) struct Args {
    /// Directory to initialise. Defaults to the current directory.
    pub path: Option<std::path::PathBuf>,
}

pub(crate) fn run(_override: Option<&Path>, args: Args) -> Result<()> {
    let target = args
        .path
        .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
    let data_dir = target.join(repo::MNEM_DIR);

    if data_dir.exists() {
        bail!(
            "already a mnem repository at {}\n\
 hint: remove the existing `.mnem/` if you meant a fresh repo, or run \
 `mnem status` to inspect it. See docs/RUNBOOK.md#6-mid-commit-crash-recovery \
 for recovery semantics.",
            data_dir.display()
        );
    }
    let (bs, ohs) = repo::create_or_open_stores(&data_dir)?;
    let r = ReadonlyRepo::init(bs, ohs)?;

    // seed `.mnem/config.toml` with sensible
    // defaults so a fresh `mnem retrieve` works out-of-the-box (with
    // a local Ollama). Idempotent: if the file already exists (e.g.
    // a parent process pre-seeded it), do NOT overwrite. The data
    // dir was created above, so we know it exists.
    let cfg_path = config::path_of(&data_dir);
    if !cfg_path.exists() {
        // Best-effort: a write failure here is not fatal (the repo
        // skeleton is already on disk), so we print a stderr hint
        // and proceed rather than rolling back the init.
        match std::fs::write(&cfg_path, DEFAULT_CONFIG_TOML) {
            Ok(()) => {
                println!(
                    " seeded config: {} ([embed] {})",
                    cfg_path.display(),
                    DEFAULT_PROVIDER_DESC
                );
            }
            Err(e) => {
                eprintln!(
                    "(warn: failed to seed {}: {e}; run `mnem config set embed.provider {}` manually)",
                    cfg_path.display(),
                    DEFAULT_PROVIDER_DESC.split(':').next().unwrap_or("ollama")
                );
            }
        }
    }

    seed_anchor_node(&r, &data_dir);

    println!("initialised mnem repo in {}", data_dir.display());
    println!(" root op: {}", r.op_id());

    // Best-effort: register this repo in ~/.mnemglobal/repos.toml so
    // `mnem` without -R can discover it. Silent if ~/.mnemglobal doesn't
    // exist yet (user hasn't run `mnem integrate`).
    crate::global::register_repo(&target);

    Ok(())
}

/// Create and initialise `.mnem/` inside `parent` without printing to stdout.
/// Idempotent: returns Ok(()) if the repo already exists.
/// Used by `mnem init` (via `run`) and `global::bootstrap` (for ~/.mnemglobal).
pub(crate) fn init_mnem_dir(parent: &Path) -> Result<()> {
    let data_dir = parent.join(repo::MNEM_DIR);
    if data_dir.exists() {
        return Ok(());
    }
    let (bs, ohs) = repo::create_or_open_stores(&data_dir)?;
    let r = ReadonlyRepo::init(bs, ohs)?;
    let cfg_path = config::path_of(&data_dir);
    if !cfg_path.exists() {
        let _ = std::fs::write(&cfg_path, DEFAULT_CONFIG_TOML);
    }
    seed_anchor_node(&r, &data_dir);
    Ok(())
}

// The anchor UUID itself lives in `mnem_core::anchor` so the same
// identity is shared by `mnem init` (the writer), `mnem reindex` (the
// skip filter), and `Query`/`Retriever` (the read-time filters).
// Re-export the parsed form locally so the rest of this file reads
// the same way it did before the move.
use mnem_core::anchor::ANCHOR_NODE_UUID as ANCHOR_NODE_ID;

/// Fixed change-id for the anchor commit so the commit CID is byte-identical
/// across all fresh repos. BUG-56: without this, the anchor commit produced by
/// `mnem init` differs between publisher and subscriber (different ChangeId
/// random bits), making the subscriber's anchor look like divergent history
/// even for a fresh subscriber who has done no real work.
///
/// The bytes spell out "mnem/anchor/v1" zero-padded to 16 bytes.
const ANCHOR_CHANGE_ID_BYTES: [u8; 16] = *b"mnem/anchor/v1\0\0";

/// Fixed commit timestamp for the anchor commit (Unix epoch). Paired with
/// `ANCHOR_CHANGE_ID_BYTES` to make the anchor commit CID deterministic.
const ANCHOR_TIME_MICROS: u64 = 0;

/// Commit a minimal anchor node to a freshly-initialised repo.
/// Non-fatal: store or embed failures are silently swallowed so they
/// never block `mnem init` or `mnem integrate`. The node makes the
/// graph non-empty from the first second so commit-history operations
/// (`log`, `blame`, `push`, `pull`) have a shared ancestor to anchor
/// against.
///
/// The anchor is structural only: it has no summary, no content, and
/// is filtered from `mnem retrieve` / `mnem query` results by default
/// (see [`mnem_core::anchor::is_system_node`] and the
/// `include_system(true)` audit opt-in on
/// [`mnem_core::index::query::Query`] /
/// [`mnem_core::retrieve::retriever::Retriever`]). The reindex
/// candidate collector also skips it, so it never accumulates a
/// fallback vector that would surface as noise in retrieve.
///
/// The anchor commit is fully deterministic (fixed node-id, change-id,
/// and timestamp) so every `mnem init` produces the same anchor commit
/// CID. This means the anchor IS an ancestor of any publisher's commit
/// chain that started with `mnem init`, enabling fast-forward pulls
/// into fresh subscriber repos (BUG-56).
fn seed_anchor_node(repo: &ReadonlyRepo, _data_dir: &std::path::Path) {
    use mnem_core::id::ChangeId;
    use mnem_core::repo::CommitOptions;

    let anchor_id = NodeId::parse_uuid(ANCHOR_NODE_ID)
        .expect("ANCHOR_NODE_ID is a valid UUID; this is a compile-time constant");
    let node = Node::new(anchor_id, "Meta");

    let mut tx = repo.start_transaction();
    match tx.add_node(&node) {
        Ok(_) => {}
        Err(_) => return,
    }

    let opts = CommitOptions::new("mnem init", "seed anchor node")
        .with_time_micros(ANCHOR_TIME_MICROS)
        .with_change_id(ChangeId::from_bytes_raw(ANCHOR_CHANGE_ID_BYTES));
    let _ = tx.commit_opts(opts);
}
