use anyhow::Result;

use crate::{types::extract_summary, GeneratedFile, GENERATED_BANNER};

use super::{
    model::{ModItemKind, ModuleGraph, ModuleNode},
    paths::{
        module_index_path, module_index_rel_link, module_item_path, module_item_rel_link,
        ITEM_DIR_CONSTANT, ITEM_DIR_ENUM, ITEM_DIR_FUNCTION, ITEM_DIR_REEXPORT, ITEM_DIR_STRUCT,
        ITEM_DIR_TRAIT, ITEM_DIR_TYPE_ALIAS, MODULE_DIR,
    },
    RenderOutput, Renderer,
};

/// Renderer that mirrors rustdoc's module tree with per-item pages.
///
/// Each module gets its own `index.md`, and individual items (functions,
/// structs, etc.) are written to dedicated pages under `fn/`, `struct/`, etc.
pub struct ModuleTreeRenderer;

impl Renderer for ModuleTreeRenderer {
    /// Render documentation using the tree layout with per-item Markdown files.
    fn render(&self, graph: &ModuleGraph, out_root: &str) -> Result<RenderOutput> {
        let mut files: Vec<GeneratedFile> = Vec::new();

        // Crate landing page linking to the crate root module.
        files.push(GeneratedFile {
            path: format!("{}/index.md", out_root),
            content: format!(
                "{GENERATED_BANNER}\n\n# `{}`\n\n- [crate root](./{}/index.md)\n\n",
                graph.crate_name, MODULE_DIR
            ),
        });

        render_module_tree(&graph.crate_name, out_root, &graph.root, &mut files);
        let summary_fragment = module_tree_summary(&graph.crate_name, out_root, &graph.root);

        Ok(RenderOutput {
            files,
            summary_fragment,
        })
    }
}

/// Recursively render a module and its children to individual files.
///
/// Creates an index page for the module, then renders each item to its own page
/// under subdirectories like `fn/`, `struct/`, etc.
fn render_module_tree(
    crate_name: &str,
    out_root: &str,
    node: &ModuleNode,
    files: &mut Vec<GeneratedFile>,
) {
    let rel_path = module_index_path(out_root, &node.fqpath);
    let mut content = format!("{GENERATED_BANNER}\n\n");
    let title = if node.fqpath.is_empty() {
        format!("`{}` crate root", crate_name)
    } else {
        format!("Module `{}`", node.fqpath)
    };
    content.push_str(&format!("# {}\n\n", title));

    if !node.docs.trim().is_empty() {
        content.push_str(node.docs.trim());
        content.push_str("\n\n");
    }

    if !node.children.is_empty() {
        content.push_str("## Modules\n\n");
        for m in &node.children {
            let link = module_index_rel_link(&node.fqpath, &m.fqpath);
            content.push_str(&format!("- [{}]({})\n", m.name, link));
        }
        content.push('\n');
    }

    if !node.items.is_empty() {
        content.push_str("## Items\n\n");
        for it in &node.items {
            let (kind_dir, kind_label) = kind_dir_label(&it.kind);
            let item_path = module_item_rel_link(&node.fqpath, &it.qualified, kind_dir, &it.name);
            let summary = extract_summary(&it.docs);
            content.push_str(&format!(
                "- [{} `{}`]({}) — {}\n",
                kind_label, it.name, item_path, summary
            ));
        }
        content.push('\n');
    }

    files.push(GeneratedFile {
        path: rel_path,
        content,
    });

    for it in &node.items {
        let (kind_dir, kind_label) = kind_dir_label(&it.kind);
        let path = module_item_path(out_root, &node.fqpath, kind_dir, &it.name);
        let mut md = format!("{GENERATED_BANNER}\n\n");
        md.push_str(&format!("# {} `{}`\n\n", kind_label, it.qualified));
        if !it.docs.trim().is_empty() {
            md.push_str(it.docs.trim());
            md.push_str("\n\n");
        }
        if let Some(sig) = &it.signature {
            md.push_str("## Signature\n\n");
            md.push_str("```rust\n");
            md.push_str(sig);
            md.push_str("\n```\n\n");
        }
        files.push(GeneratedFile { path, content: md });
    }

    for m in &node.children {
        render_module_tree(crate_name, out_root, m, files);
    }
}

/// Build SUMMARY.md fragment for the module tree.
fn module_tree_summary(crate_name: &str, out_root: &str, node: &ModuleNode) -> String {
    let mut s = String::new();
    s.push_str(&format!(
        "  - [`{}`](./{}/index.md)\n",
        crate_name, out_root
    ));
    s.push_str(&format!(
        "    - [crate root](./{}/{}/index.md)\n",
        out_root, MODULE_DIR
    ));
    for m in &node.children {
        push_module_summary(out_root, m, 2, &mut s);
    }
    s
}

/// Recursively append module entries (and their items) to SUMMARY.md fragment.
fn push_module_summary(out_root: &str, node: &ModuleNode, indent: usize, buf: &mut String) {
    let indent_str = "  ".repeat(indent);
    let rel = format!(
        "./{}/{}/{}/index.md",
        out_root,
        MODULE_DIR,
        node.fqpath.replace("::", "/")
    );
    buf.push_str(&format!("{}- [`{}`]({})\n", indent_str, node.name, rel));

    for it in &node.items {
        let (dir, label) = kind_dir_label(&it.kind);
        let rel_item = format!(
            "./{}/{}/{}/{}/{}.md",
            out_root,
            MODULE_DIR,
            node.fqpath.replace("::", "/"),
            dir,
            it.name
        );
        buf.push_str(&format!(
            "{}  - [{} `{}`]({})\n",
            indent_str, label, it.name, rel_item
        ));
    }

    for m in &node.children {
        push_module_summary(out_root, m, indent + 1, buf);
    }
}

/// Map item kind to directory name and label.
///
/// Returns `(directory, label)` tuples like `("fn", "fn")` or `("struct", "struct")`.
fn kind_dir_label(kind: &ModItemKind) -> (&'static str, &'static str) {
    match kind {
        ModItemKind::Function => (ITEM_DIR_FUNCTION, "fn"),
        ModItemKind::Struct => (ITEM_DIR_STRUCT, "struct"),
        ModItemKind::Enum => (ITEM_DIR_ENUM, "enum"),
        ModItemKind::Trait => (ITEM_DIR_TRAIT, "trait"),
        ModItemKind::Constant => (ITEM_DIR_CONSTANT, "const"),
        ModItemKind::TypeAlias => (ITEM_DIR_TYPE_ALIAS, "type"),
        ModItemKind::ReExport => (ITEM_DIR_REEXPORT, "re-export"),
    }
}
