//! Emit `ImportedSkill` as a proposed-primitive markdown.
//!
//! This output is INTENDED for human review — it is NOT a real
//! primitive. The file lives at `_primitives/proposed/<name>.md`
//! and contains:
//!   - classification report (atoms-found / unresolved / why-emerge)
//!   - suggested `MANIFEST.toml` stanza in a fenced TOML block
//!   - suggested `Cargo.toml` skeleton in a fenced TOML block
//!   - body of the original skill quoted verbatim
//!
//! The reviewer decides: accept (turn into real primitive) / reject /
//! defer / split-into-recipe.

use crate::canonical::{AtomCallKind, ImportedSkill};
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};

/// Render `skill` as proposed-primitive markdown text. Side-effect-free.
pub fn render(skill: &ImportedSkill) -> Result<String> {
    let crate_name = derive_crate_name(skill);
    let mut s = String::new();

    s.push_str(&format!("# Proposed primitive: `{crate_name}`\n\n"));
    s.push_str(&format!("**Source**: `{}`\n", skill.source_path.display()));
    s.push_str(&format!(
        "**Format**: `{}`\n",
        skill.source_format.as_str()
    ));
    s.push_str(&format!(
        "**Imported at**: `{}`\n\n",
        chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ")
    ));

    s.push_str("> NOT YET A REAL PRIMITIVE. This is a proposal generated by\n");
    s.push_str("> `kei-skill-importer`. A human must accept/reject before\n");
    s.push_str("> the crate is created and added to the workspace.\n\n");

    s.push_str(&render_classification(skill));
    s.push_str(&render_manifest_stanza(&crate_name, &skill.description));
    s.push_str(&render_cargo_skeleton(&crate_name));
    s.push_str(&render_original(skill));

    Ok(s)
}

/// Render + write to `<output_dir>/proposed/<crate>.md`.
pub fn write(skill: &ImportedSkill, output_dir: &Path) -> Result<PathBuf> {
    let text = render(skill)?;
    let dir = output_dir.join("proposed");
    std::fs::create_dir_all(&dir)
        .with_context(|| format!("create_dir_all {}", dir.display()))?;
    let file = dir.join(format!("{}.md", derive_crate_name(skill)));
    std::fs::write(&file, text)
        .with_context(|| format!("write {}", file.display()))?;
    Ok(file)
}

fn render_classification(skill: &ImportedSkill) -> String {
    let total = skill.total_atom_calls();
    let resolved = skill.resolved_atom_calls();
    let unresolved = total - resolved;
    let bash_count = skill
        .phases
        .iter()
        .flat_map(|p| p.atom_calls.iter())
        .filter(|c| c.kind == AtomCallKind::Bash)
        .count();
    let kei_count = skill
        .phases
        .iter()
        .flat_map(|p| p.atom_calls.iter())
        .filter(|c| c.kind == AtomCallKind::KeiPrimitive)
        .count();
    let slash_count = skill
        .phases
        .iter()
        .flat_map(|p| p.atom_calls.iter())
        .filter(|c| c.kind == AtomCallKind::UserPrompt)
        .count();

    let mut s = String::from("## Classification report\n\n");
    s.push_str(&format!("- Phases: **{}**\n", skill.phases.len()));
    s.push_str(&format!("- Total atom calls: **{total}**\n"));
    s.push_str(&format!("- Resolved against registry: **{resolved}**\n"));
    s.push_str(&format!("- Unresolved (novel verbs): **{unresolved}**\n"));
    s.push_str(&format!("- Bash invocations: **{bash_count}**\n"));
    s.push_str(&format!("- Kei-primitive invocations: **{kei_count}**\n"));
    s.push_str(&format!("- Slash commands: **{slash_count}**\n\n"));
    s.push_str("### Why emerge as primitive\n\n");
    if unresolved > 0 {
        s.push_str(&format!(
            "{} unresolved kei-primitive verb(s) — registry doesn't know them yet.\n\n",
            unresolved
        ));
    } else if total == 0 && bash_count == 0 {
        s.push_str("Zero atom calls and no bash — pure prose skill, candidate for direct rule/wikification instead.\n\n");
    } else if bash_count > 0 && kei_count == 0 {
        s.push_str("Bash-only wrapper — promote into shell primitive (kind=\"shell\").\n\n");
    } else {
        s.push_str("Default fallback — review whether this should be a recipe instead.\n\n");
    }
    s
}

fn render_manifest_stanza(crate_name: &str, desc: &str) -> String {
    let safe_desc = desc.replace('"', "\\\"").lines().next().unwrap_or("").to_string();
    format!(
        "## Suggested `MANIFEST.toml` stanza\n\n\
         ```toml\n\
         [primitive.{crate_name}]\n\
         kind = \"rust\"\n\
         crate = \"{crate_name}\"\n\
         deps = []\n\
         desc = \"{safe_desc}\"\n\
         ```\n\n"
    )
}

fn render_cargo_skeleton(crate_name: &str) -> String {
    format!(
        "## Suggested `Cargo.toml` skeleton\n\n\
         ```toml\n\
         [package]\n\
         name = \"{crate_name}\"\n\
         version = \"0.1.0\"\n\
         edition = \"2021\"\n\
         rust-version = \"1.75\"\n\
         \n\
         [[bin]]\n\
         name = \"{crate_name}\"\n\
         path = \"src/main.rs\"\n\
         \n\
         [dependencies]\n\
         clap = {{ workspace = true }}\n\
         anyhow = \"1\"\n\
         ```\n\n"
    )
}

fn render_original(skill: &ImportedSkill) -> String {
    let mut s = String::from("## Original skill body (verbatim)\n\n");
    for line in skill.body.lines() {
        s.push_str("> ");
        s.push_str(line);
        s.push('\n');
    }
    s.push('\n');
    s
}

fn derive_crate_name(skill: &ImportedSkill) -> String {
    let raw = skill.name.trim().to_ascii_lowercase();
    let mut out = String::with_capacity(raw.len() + 4);
    out.push_str("kei-imported-");
    let mut prev_dash = false;
    for c in raw.chars() {
        if c.is_ascii_alphanumeric() {
            out.push(c);
            prev_dash = false;
        } else if (c == '-' || c == '_' || c == ' ') && !prev_dash {
            out.push('-');
            prev_dash = true;
        }
    }
    let t = out.trim_end_matches('-').to_string();
    if t == "kei-imported-" || t == "kei-imported" {
        "kei-imported-skill".into()
    } else {
        t
    }
}
