//! Navigation metadata generation for API documentation.

use std::path::Path;

use serde::{Deserialize, Serialize};

const DEFAULT_BASE_PATH: &str = "/api";
const DEFAULT_EXPORT_NAME: &str = "apiNav";
const OVERVIEW_TITLE: &str = "Overview";

/// Navigation item for generated documentation sidebars.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DocsNavItem {
    /// Display title for the navigation item.
    pub title: String,
    /// Path to the documentation page.
    pub path: String,
    /// Child navigation items.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub children: Option<Vec<DocsNavItem>>,
}

/// Generates sidebar navigation metadata from documentation file paths.
pub fn generate_nav_metadata(files: &[String], base_path: Option<&str>) -> Vec<DocsNavItem> {
    let base_path = base_path.unwrap_or(DEFAULT_BASE_PATH);
    let mut sorted_files = files.to_vec();

    sorted_files.sort_by_key(|file| get_doc_display_name(file));

    sorted_files
        .into_iter()
        .map(|file| DocsNavItem {
            title: get_doc_display_name(&file),
            path: format!("{base_path}/{}", get_doc_file_name(&file)),
            children: None,
        })
        .collect()
}

/// Generates TypeScript source code for navigation metadata exports.
pub fn generate_nav_code(nav_items: &[DocsNavItem], export_name: Option<&str>) -> String {
    let export_name = export_name.unwrap_or(DEFAULT_EXPORT_NAME);
    let json = serde_json::to_string_pretty(nav_items).unwrap_or_else(|_| "[]".to_string());

    format!(
        r"/**
 * Auto-generated API documentation navigation.
 * This file is automatically generated by the docs plugin.
 * Do not edit manually.
 */

export interface NavItem {{
  title: string;
  path: string;
  children?: NavItem[];
}}

export const {export_name}: NavItem[] = {json} as const;
"
    )
}

fn get_doc_display_name(file_path: &str) -> String {
    let file_name = file_stem(file_path);

    if file_name == "index" || file_name == "index-module" {
        return OVERVIEW_TITLE.to_string();
    }

    format_doc_title(&file_name)
}

fn get_doc_file_name(file_path: &str) -> String {
    file_stem(file_path)
}

fn file_stem(file_path: &str) -> String {
    Path::new(file_path).file_stem().and_then(|stem| stem.to_str()).unwrap_or_default().to_string()
}

fn format_doc_title(name: &str) -> String {
    let mut chars = name.chars().peekable();
    let mut result = String::new();

    while let Some(ch) = chars.next() {
        if matches!(ch, '-' | '_') {
            match chars.peek().copied() {
                Some(next) if next.is_ascii_lowercase() => {
                    result.push(' ');
                    result.push(next.to_ascii_uppercase());
                    chars.next();
                }
                _ => result.push(ch),
            }
        } else {
            result.push(ch);
        }
    }

    if let Some(first) = result.chars().next().filter(char::is_ascii_lowercase) {
        let uppercase = first.to_ascii_uppercase().to_string();
        result.replace_range(0..first.len_utf8(), &uppercase);
    }

    result
}

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

    #[test]
    fn generates_nav_metadata_from_file_paths() {
        let nav = generate_nav_metadata(
            &[
                "/repo/src/types.ts".to_string(),
                "/repo/src/index.ts".to_string(),
                "/repo/src/nav-generator.ts".to_string(),
            ],
            Some("/api"),
        );

        assert_eq!(
            nav,
            vec![
                DocsNavItem {
                    title: "Nav Generator".to_string(),
                    path: "/api/nav-generator".to_string(),
                    children: None,
                },
                DocsNavItem {
                    title: "Overview".to_string(),
                    path: "/api/index".to_string(),
                    children: None,
                },
                DocsNavItem {
                    title: "Types".to_string(),
                    path: "/api/types".to_string(),
                    children: None,
                },
            ]
        );
    }

    #[test]
    fn generates_nav_code() {
        let code = generate_nav_code(
            &[DocsNavItem {
                title: "Docs".to_string(),
                path: "/api/docs".to_string(),
                children: None,
            }],
            Some("apiNav"),
        );

        assert!(code.contains("export const apiNav: NavItem[] = ["));
        assert!(code.contains(r#""title": "Docs""#));
        assert!(code.ends_with(" as const;\n"));
    }
}
