//! memex: Generate mdBook-ready Markdown from rustdoc JSON.
//!
//! Transforms [rustdoc JSON output](https://doc.rust-lang.org/nightly/rustdoc/json.html)
//! into structured Markdown files for [mdBook](https://rust-lang.github.io/mdBook/).
//!
//! # Quick Start
//!
//! 1. Generate rustdoc JSON:
//!    ```bash
//!    cargo +nightly rustdoc --lib -- -Z unstable-options --output-format json
//!    ```
//!
//! 2. Create `memex.toml`:
//!    ```toml
//!    [[targets]]
//!    crate = "my_crate"
//!    out = "api/my_crate"
//!    layout = "module-tree"
//!    ```
//!
//! 3. Generate docs:
//!    ```no_run
//!    use memex::{parse_targets_toml, render_targets};
//!    use std::{fs, path::Path};
//!
//!    # fn main() -> anyhow::Result<()> {
//!    let toml = fs::read_to_string("docs/memex.toml")?;
//!    let targets = parse_targets_toml(
//!        Path::new("."),
//!        Path::new("docs"),
//!        Path::new("target/doc"),
//!        &toml,
//!    )?;
//!    let docs = render_targets(&targets)?;
//!    // Write docs.files to disk...
//!    # Ok(())
//!    # }
//!    ```
//!
//! # Layouts
//!
//! - **`module-tree`**: Per-item pages in nested directories (like rustdoc)
//! - **`module-single`**: One page per module with grouped sections
/// Tools for constructing agent-facing documentation bundles and helpers.
pub mod agents;
/// Parsing and normalization logic for `memex.toml`.
pub mod config;
pub mod error;
mod modtree;
/// High-level orchestration pipeline for building documentation with memex.
pub mod pipeline;
mod scan;
pub mod target;
mod types;

use std::path::Path;

use anyhow::Result;

pub use config::parse_targets_toml;
use modtree::{
    build_module_graph, ModuleGraph, ModuleSingleRenderer, ModuleTreeRenderer, RenderOutput,
    Renderer,
};
pub use pipeline::{build_site, build_site_with_config_path};
use scan::collect_crate_paths;
use target::validate_targets;
pub use target::{Layout, TargetSpec};

/// Marker inserted at the top of generated Markdown files.
pub const GENERATED_BANNER: &str = "<!-- Generated by memex; do not edit manually. -->";

/// A generated documentation file with its relative path and content.
#[derive(Debug, Clone)]
pub struct GeneratedFile {
    /// Relative path from docs/src/ (e.g., "api/auth-flow/index.md")
    pub path: String,
    /// Markdown content for this file
    pub content: String,
}

/// Complete documentation output including all files and SUMMARY.md fragment.
#[derive(Debug)]
pub struct GeneratedDocs {
    /// All generated markdown files
    pub files: Vec<GeneratedFile>,
    /// SUMMARY.md fragment to be inserted under "API Reference"
    pub summary_fragment: String,
}

/// Auto-discover rustdoc JSON files and create targets.
///
/// Scans `json_dir` for `.json` files and creates a [`TargetSpec`] for each one.
/// Crates in `skip_crates` are excluded.
///
/// # Example
///
/// ```no_run
/// use memex::{scan_targets, Layout};
/// use std::path::Path;
///
/// # fn main() -> anyhow::Result<()> {
/// let targets = scan_targets(
///     Path::new("target/doc"),
///     Layout::ModuleSingle,
///     &["build_script"],
///     "api/libs"
/// )?;
/// # Ok(())
/// # }
/// ```
pub fn scan_targets<P: AsRef<Path>>(
    json_dir: P,
    default_layout: Layout,
    skip_crates: &[&str],
    out_root_prefix: &str,
) -> Result<Vec<TargetSpec>> {
    let paths = collect_crate_paths(json_dir, skip_crates)?;
    let mut targets = Vec::new();
    for p in paths {
        let stem = p
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("unknown")
            .to_string();
        let out_root = if out_root_prefix.is_empty() {
            format!("api/{}", stem)
        } else {
            format!("{}/{}", out_root_prefix.trim_end_matches('/'), stem)
        };
        targets.push(TargetSpec::new(p, out_root)?.with_layout(default_layout.clone()));
    }
    Ok(targets)
}

/// Render Markdown documentation from configured targets.
///
/// Processes each [`TargetSpec`], builds module graphs from rustdoc JSON,
/// and generates Markdown files with a SUMMARY.md fragment.
///
/// # Example
///
/// ```no_run
/// use memex::{TargetSpec, Layout, render_targets};
/// use std::path::Path;
///
/// # fn main() -> anyhow::Result<()> {
/// let targets = vec![
///     TargetSpec::new("target/doc/my_crate.json", "api/my_crate")?
///         .with_layout(Layout::ModuleSingle),
/// ];
///
/// let docs = render_targets(&targets)?;
/// // Write docs.files to disk...
/// # Ok(())
/// # }
/// ```
pub fn render_targets(targets: &[TargetSpec]) -> Result<GeneratedDocs> {
    let mut all_files: Vec<GeneratedFile> = Vec::new();
    let mut fragment = String::from("- [API Reference](./api/index.md)\n");
    let mut index_list: Vec<(String, String)> = Vec::new(); // (crate_name, out_root)

    validate_targets(targets)?;

    for t in targets {
        if let Some(graph) = build_module_graph(&t.json_path, t.include_private)? {
            let crate_name = graph.crate_name.clone();
            let output = render_with_layout(t.layout.clone(), &graph, &t.out_root)?;
            all_files.extend(output.files);
            fragment.push_str(&output.summary_fragment);
            index_list.push((crate_name, t.out_root.clone()));
        }
    }

    // Minimal API index page
    let mut api_index = format!("{GENERATED_BANNER}\n\n# API Reference\n\n");
    for (name, root) in &index_list {
        api_index.push_str(&format!("- [`{}`](./{}/index.md)\n", name, root));
    }
    all_files.push(GeneratedFile {
        path: "api/index.md".to_string(),
        content: api_index,
    });

    Ok(GeneratedDocs {
        files: all_files,
        summary_fragment: fragment,
    })
}

/// Dispatch to the appropriate renderer for the requested layout strategy.
fn render_with_layout(layout: Layout, graph: &ModuleGraph, out_root: &str) -> Result<RenderOutput> {
    match layout {
        Layout::ModuleTree => ModuleTreeRenderer.render(graph, out_root),
        Layout::ModuleSingle => ModuleSingleRenderer.render(graph, out_root),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn generated_docs_structure() {
        // This test just verifies the structure compiles and types work
        let file = GeneratedFile {
            path: "api/test/index.md".to_string(),
            content: "# Test".to_string(),
        };
        assert_eq!(file.path, "api/test/index.md");
    }
}
