//! Content Processor
//!
//! Configuration-driven content processor that generates:
//! 1. post-name.html (markdown content converted to HTML)
//! 2. post-name.json (markdown frontmatter in JSON format)
//! 3. index.json (collection of all posts with frontmatter + file paths)
//!
//! Supports:
//! - Hot reload capability for file change detection
//! - Dynamic content type discovery (no hardcoded types)
//! - Multi-language support
//! - Environment-driven configuration

use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::SystemTime;

use anyhow::{Context, Result};
use clap::{Arg, Command};
use glob::glob;
use serde::{Deserialize, Serialize};
use serde_json;
use toml;

/// Content processor configuration
#[derive(Debug, Clone)]
pub struct ProcessorConfig {
    pub content_source_path: PathBuf,
    pub static_output_path: PathBuf,
    pub enabled_content_types: Vec<String>,
    pub enabled_languages: Vec<String>,
    pub watch_mode: bool,
}

/// Frontmatter data structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Frontmatter {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slug: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub category: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tags: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub excerpt: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub date: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub author: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub published: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub featured: Option<bool>,
    #[serde(flatten)]
    pub additional_fields: HashMap<String, serde_json::Value>,
}

/// Post entry in index.json
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostEntry {
    #[serde(flatten)]
    pub frontmatter: Frontmatter,
    pub html_file: String,
    pub json_file: String,
    pub last_modified: u64,
}

/// Content index structure
#[derive(Debug, Serialize, Deserialize)]
pub struct ContentIndex {
    pub generated_at: String,
    pub content_type: String,
    pub language: String,
    pub posts: Vec<PostEntry>,
    pub total: usize,
}

/// Filter index structure for categories and tags with emoji data
#[derive(Debug, Serialize, Deserialize)]
pub struct FilterIndex {
    pub generated_at: String,
    pub content_type: String,
    pub language: String,
    pub total_posts: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub info: Option<serde_json::Value>,
    pub tags: HashMap<String, TagData>,
    pub categories: HashMap<String, CategoryData>,
}

/// Category data with emoji
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CategoryData {
    pub emoji: String,
}

/// Tag data with emoji
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TagData {
    pub emoji: String,
}

/// Content manifest structure
#[derive(Debug, Serialize, Deserialize)]
pub struct ContentManifest {
    pub generated_at: String,
    pub version: String,
    pub languages: Vec<String>,
    pub content_types: Vec<String>,
    pub content_root_path: String,
    pub build_info: BuildInfo,
    pub structure: HashMap<String, HashMap<String, ContentTypeInfo>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BuildInfo {
    pub source_format: String,
    pub output_format: String,
    pub converter: String,
    pub features: Vec<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ContentTypeInfo {
    pub html_count: usize,
    pub markdown_count: usize,
    pub has_index: bool,
}

impl ProcessorConfig {
    /// Create configuration from environment variables
    pub fn from_env() -> Result<Self> {
        let content_source =
            std::env::var("SITE_CONTENT_PATH").unwrap_or_else(|_| "site/content".to_string());

        // For build process, generate to SITE_PUBLIC_PATH
        let public_path =
            std::env::var("SITE_PUBLIC_PATH").unwrap_or_else(|_| "site/public".to_string());

        let static_output = format!(
            "{}/{}",
            &public_path,
            std::env::var("SITE_SERVER_ROOT_CONTENT").unwrap_or_else(|_| "r".to_string())
        );

        // Discover enabled content types from content-kinds.toml
        let content_kinds_path = Path::new(&content_source).join("content-kinds.toml");
        let enabled_content_types = if content_kinds_path.exists() {
            discover_content_types(&content_kinds_path)?
        } else {
            vec!["blog".to_string(), "recipes".to_string()] // fallback defaults
        };

        // Discover languages from directory structure
        let enabled_languages = discover_languages(&content_source, &enabled_content_types)?;

        Ok(ProcessorConfig {
            content_source_path: PathBuf::from(content_source),
            static_output_path: PathBuf::from(static_output),
            enabled_content_types,
            enabled_languages,
            watch_mode: false,
        })
    }
}

/// Discover content types from content-kinds.toml
fn discover_content_types(content_kinds_path: &Path) -> Result<Vec<String>> {
    let content =
        fs::read_to_string(content_kinds_path).context("Failed to read content-kinds.toml")?;

    let config: toml::Value =
        toml::from_str(&content).context("Failed to parse content-kinds.toml")?;

    let mut types = Vec::new();

    if let Some(content_kinds) = config.get("content_kinds").and_then(|v| v.as_array()) {
        for kind in content_kinds {
            if let (Some(name), Some(enabled)) = (
                kind.get("name").and_then(|v| v.as_str()),
                kind.get("enabled").and_then(|v| v.as_bool()),
            ) {
                if enabled {
                    types.push(name.to_string());
                }
            }
        }
    }

    if types.is_empty() {
        types = vec!["blog".to_string(), "recipes".to_string()];
    }

    Ok(types)
}

/// Discover languages from directory structure
fn discover_languages(content_source: &str, content_types: &[String]) -> Result<Vec<String>> {
    let mut languages = Vec::new();

    for content_type in content_types {
        let type_path = Path::new(content_source).join(content_type);
        if type_path.exists() && type_path.is_dir() {
            for entry in fs::read_dir(type_path)? {
                let entry = entry?;
                if entry.file_type()?.is_dir() {
                    if let Some(lang) = entry.file_name().to_str() {
                        if !languages.contains(&lang.to_string()) {
                            languages.push(lang.to_string());
                        }
                    }
                }
            }
        }
    }

    if languages.is_empty() {
        languages = vec!["en".to_string(), "es".to_string()];
    }

    Ok(languages)
}

/// Parse frontmatter from markdown content
fn parse_frontmatter(content: &str) -> Result<(Frontmatter, String)> {
    let lines: Vec<&str> = content.lines().collect();

    if lines.first() != Some(&"---") {
        return Ok((Frontmatter::default(), content.to_string()));
    }

    let mut frontmatter_end = 0;
    for (i, line) in lines.iter().enumerate().skip(1) {
        if *line == "---" {
            frontmatter_end = i;
            break;
        }
    }

    if frontmatter_end == 0 {
        return Ok((Frontmatter::default(), content.to_string()));
    }

    let frontmatter_text = lines[1..frontmatter_end].join("\n");
    let markdown_content = lines[frontmatter_end + 1..].join("\n");

    let frontmatter: Frontmatter =
        serde_yaml::from_str(&frontmatter_text).context("Failed to parse frontmatter YAML")?;

    Ok((frontmatter, markdown_content))
}

impl Default for Frontmatter {
    fn default() -> Self {
        Self {
            id: None,
            title: None,
            slug: None,
            category: None,
            tags: None,
            excerpt: None,
            date: None,
            author: None,
            published: Some(true),
            featured: Some(false),
            additional_fields: HashMap::new(),
        }
    }
}

/// Convert markdown to HTML
fn markdown_to_html(markdown: &str) -> String {
    // Use pulldown-cmark for markdown processing
    use pulldown_cmark::{html, Options, Parser};

    let mut options = Options::empty();
    options.insert(Options::ENABLE_STRIKETHROUGH);
    options.insert(Options::ENABLE_TABLES);
    options.insert(Options::ENABLE_FOOTNOTES);
    options.insert(Options::ENABLE_TASKLISTS);

    let parser = Parser::new_ext(markdown, options);
    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);

    html_output
}

/// Process a single markdown file
fn process_markdown_file(
    file_path: &Path,
    output_dir: &Path,
    relative_path: &str,
) -> Result<PostEntry> {
    println!("📄 Processing: {}", file_path.display());

    let content = fs::read_to_string(file_path)
        .context(format!("Failed to read file: {}", file_path.display()))?;

    let (frontmatter, markdown_content) = parse_frontmatter(&content)?;

    // Get file stem for naming
    let file_stem = file_path
        .file_stem()
        .and_then(|s| s.to_str())
        .context("Invalid filename")?;

    // 1. Generate HTML file (post-name.html)
    let html_content = markdown_to_html(&markdown_content);
    let html_filename = format!("{}.html", file_stem);
    let html_path = output_dir.join(&html_filename);

    fs::create_dir_all(html_path.parent().unwrap())?;
    fs::write(&html_path, html_content).context("Failed to write HTML file")?;

    // 2. Generate JSON file (post-name.json) - frontmatter only
    let json_filename = format!("{}.json", file_stem);
    let json_path = output_dir.join(&json_filename);
    let frontmatter_json =
        serde_json::to_string_pretty(&frontmatter).context("Failed to serialize frontmatter")?;

    fs::write(&json_path, frontmatter_json).context("Failed to write JSON file")?;

    // Get last modified time
    let last_modified = file_path
        .metadata()?
        .modified()?
        .duration_since(SystemTime::UNIX_EPOCH)?
        .as_secs();

    println!("  ✅ Generated: {} and {}", html_filename, json_filename);

    Ok(PostEntry {
        frontmatter,
        html_file: format!("{}/{}", relative_path, html_filename),
        json_file: format!("{}/{}", relative_path, json_filename),
        last_modified,
    })
}

/// Process content for a specific type and language
fn process_content_type_language(
    config: &ProcessorConfig,
    content_type: &str,
    language: &str,
) -> Result<()> {
    println!("🔄 Processing {} content for {}", content_type, language);

    let source_dir = config.content_source_path.join(content_type).join(language);

    let output_dir = config.static_output_path.join(content_type).join(language);

    if !source_dir.exists() {
        println!("  ⚠️  No {} content found for {}", content_type, language);
        return Ok(());
    }

    // Create output directory
    fs::create_dir_all(&output_dir)?;

    let mut posts = Vec::new();

    // Process all markdown files recursively
    process_directory_recursive(&source_dir, &output_dir, &mut posts, "")?;

    // Remove rendered outputs whose source markdown no longer exists (posts that
    // were renamed or deleted). Scoped to this (type, language) output subtree, so
    // a targeted --content-type/--language run only prunes within its own scope.
    prune_orphaned_outputs(&source_dir, &output_dir)?;

    // Filter published posts
    posts.retain(|post| post.frontmatter.published.unwrap_or(true));

    // Sort by date (newest first)
    posts.sort_by(|a, b| {
        let date_a = a.frontmatter.date.as_deref().unwrap_or("");
        let date_b = b.frontmatter.date.as_deref().unwrap_or("");
        date_b.cmp(date_a)
    });

    // 3. Generate index.json
    let index = ContentIndex {
        generated_at: chrono::Utc::now().to_rfc3339(),
        content_type: content_type.to_string(),
        language: language.to_string(),
        total: posts.len(),
        posts,
    };

    let index_path = output_dir.join("index.json");
    let index_json =
        serde_json::to_string_pretty(&index).context("Failed to serialize content index")?;

    fs::write(index_path, index_json)?;

    println!("  ✅ Generated index.json with {} posts", index.total);

    // Generate category-specific indexes with full frontmatter
    generate_category_indexes_for_posts(config, content_type, language, &index.posts)?;

    // Generate filter-index.json with tags and categories
    generate_filter_index(config, content_type, language, &index.posts)?;

    Ok(())
}

/// Process directory recursively
fn process_directory_recursive(
    source_dir: &Path,
    output_dir: &Path,
    posts: &mut Vec<PostEntry>,
    relative_path: &str,
) -> Result<()> {
    for entry in fs::read_dir(source_dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            let dir_name = path
                .file_name()
                .and_then(|s| s.to_str())
                .context("Invalid directory name")?;

            let new_relative_path = if relative_path.is_empty() {
                dir_name.to_string()
            } else {
                format!("{}/{}", relative_path, dir_name)
            };

            let new_output_dir = output_dir.join(dir_name);
            fs::create_dir_all(&new_output_dir)?;

            process_directory_recursive(&path, &new_output_dir, posts, &new_relative_path)?;
        } else if path.extension().and_then(|s| s.to_str()) == Some("md") {
            let post_entry = process_markdown_file(&path, output_dir, relative_path)?;
            posts.push(post_entry);
        }
    }

    Ok(())
}

/// Remove rendered outputs orphaned by a renamed or deleted source file.
///
/// Each post emits the pair `<stem>.html` + `<stem>.json`, named after its
/// source `<stem>.md`. Aggregates (`index.json`, `filter-index.json`, category
/// indexes) are `.json` files with no `.html` sibling, so anchoring the sweep
/// on `.html` files leaves them untouched by construction — no reserved-name
/// allowlist needed. The output tree mirrors the source tree, so
/// `<sub>/<stem>.html` maps back to `<source_dir>/<sub>/<stem>.md`; a missing
/// source removes the pair. Emptied mirror directories (whose source directory
/// is gone) are removed as well.
fn prune_orphaned_outputs(source_dir: &Path, output_dir: &Path) -> Result<()> {
    if !output_dir.exists() {
        return Ok(());
    }

    for entry in fs::read_dir(output_dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            let dir_name = path
                .file_name()
                .and_then(|s| s.to_str())
                .context("Invalid directory name")?;
            let source_subdir = source_dir.join(dir_name);

            prune_orphaned_outputs(&source_subdir, &path)?;

            // Drop the mirror directory once it is empty and its source is gone.
            if !source_subdir.exists() && fs::read_dir(&path)?.next().is_none() {
                fs::remove_dir(&path)?;
            }
        } else if path.extension().and_then(|s| s.to_str()) == Some("html") {
            let stem = path
                .file_stem()
                .and_then(|s| s.to_str())
                .context("Invalid output filename")?;

            if !source_dir.join(format!("{stem}.md")).exists() {
                fs::remove_file(&path)?;
                let json_sibling = path.with_extension("json");
                if json_sibling.exists() {
                    fs::remove_file(&json_sibling)?;
                }
                println!("  🗑️  Pruned orphaned output: {}", path.display());
            }
        }
    }

    Ok(())
}

/// Process specific files by pattern
fn process_specific_files(config: &ProcessorConfig, file_pattern: &str) -> Result<()> {
    println!("🎯 Processing specific files matching: {}", file_pattern);

    // Build full pattern with source path
    let full_pattern = config.content_source_path.join(file_pattern);
    let pattern_str = full_pattern.to_str().context("Invalid file pattern path")?;

    let matched_files: Result<Vec<_>, _> = glob(pattern_str)?.collect();
    let matched_files = matched_files.context("Failed to process glob pattern")?;

    if matched_files.is_empty() {
        println!("⚠️  No files found matching pattern: {}", file_pattern);
        return Ok(());
    }

    println!("📄 Found {} matching files", matched_files.len());

    for file_path in matched_files {
        if file_path.extension().and_then(|s| s.to_str()) != Some("md") {
            continue;
        }

        // Determine content type and language from path structure
        let relative_path = file_path.strip_prefix(&config.content_source_path)?;
        let path_parts: Vec<_> = relative_path.components().collect();

        if path_parts.len() < 2 {
            println!(
                "⚠️  Skipping file with invalid path structure: {}",
                file_path.display()
            );
            continue;
        }

        let content_type = path_parts[0].as_os_str().to_str().unwrap_or("unknown");
        let language = path_parts[1].as_os_str().to_str().unwrap_or("unknown");

        // Create output directory structure
        let mut output_path = config.static_output_path.join(content_type).join(language);

        // Add category subdirectories if they exist
        if path_parts.len() > 3 {
            for part in &path_parts[2..path_parts.len() - 1] {
                output_path = output_path.join(part.as_os_str());
            }
        }

        fs::create_dir_all(&output_path)?;

        // Determine relative path for URLs
        let url_relative_path = if path_parts.len() > 3 {
            path_parts[2..path_parts.len() - 1]
                .iter()
                .map(|p| p.as_os_str().to_str().unwrap_or(""))
                .collect::<Vec<_>>()
                .join("/")
        } else {
            String::new()
        };

        // Process the single file
        let post_entry = process_markdown_file(&file_path, &output_path, &url_relative_path)?;

        // Update the corresponding index.json
        update_index_for_post(
            &config,
            content_type,
            language,
            &post_entry,
            &url_relative_path,
        )?;

        println!("✅ Processed: {}", file_path.display());
    }

    println!("🎉 File-specific processing complete!");
    Ok(())
}

/// Process specific category (subdirectory within content-type/language)
fn process_specific_category(config: &ProcessorConfig, category: &str) -> Result<()> {
    println!("🏷️  Processing specific category: {}", category);

    for content_type in &config.enabled_content_types {
        for language in &config.enabled_languages {
            let category_path = config
                .content_source_path
                .join(content_type)
                .join(language)
                .join(category);

            if !category_path.exists() {
                continue;
            }

            println!(
                "📁 Processing category '{}' for {} content in {}",
                category, content_type, language
            );

            let output_dir = config
                .static_output_path
                .join(content_type)
                .join(language)
                .join(category);

            fs::create_dir_all(&output_dir)?;

            let mut posts = Vec::new();

            // Process all markdown files in the category
            process_directory_recursive(&category_path, &output_dir, &mut posts, category)?;

            // Update main index with category posts
            for post in &posts {
                update_index_for_post(config, content_type, language, post, category)?;
            }

            // Generate category-specific index.json with full frontmatter
            generate_category_index(config, content_type, language, category, &posts)?;

            println!(
                "✅ Category '{}' processed for {} in {}",
                category, content_type, language
            );
        }
    }

    println!("🎉 Category-specific processing complete!");
    Ok(())
}

/// Update index.json with a specific post entry
fn update_index_for_post(
    config: &ProcessorConfig,
    content_type: &str,
    language: &str,
    post_entry: &PostEntry,
    _relative_category: &str,
) -> Result<()> {
    let index_path = config
        .static_output_path
        .join(content_type)
        .join(language)
        .join("index.json");

    let mut content_index = if index_path.exists() {
        let existing_content = fs::read_to_string(&index_path)?;
        serde_json::from_str::<ContentIndex>(&existing_content)?
    } else {
        ContentIndex {
            generated_at: chrono::Utc::now().to_rfc3339(),
            content_type: content_type.to_string(),
            language: language.to_string(),
            posts: Vec::new(),
            total: 0,
        }
    };

    // Remove existing entry with same ID if it exists
    if let Some(id) = &post_entry.frontmatter.id {
        content_index
            .posts
            .retain(|p| p.frontmatter.id.as_ref() != Some(id));
    }

    // Add the new/updated post
    content_index.posts.push(post_entry.clone());

    // Filter published posts and sort by date
    content_index
        .posts
        .retain(|post| post.frontmatter.published.unwrap_or(true));
    content_index.posts.sort_by(|a, b| {
        let date_a = a.frontmatter.date.as_deref().unwrap_or("");
        let date_b = b.frontmatter.date.as_deref().unwrap_or("");
        date_b.cmp(date_a)
    });

    // Update metadata
    content_index.generated_at = chrono::Utc::now().to_rfc3339();
    content_index.total = content_index.posts.len();

    // Write updated index
    let index_json = serde_json::to_string_pretty(&content_index)?;
    fs::write(&index_path, index_json)?;

    println!("  📋 Updated index.json with {} posts", content_index.total);

    Ok(())
}

/// Generate category-specific index.json with full frontmatter fields
fn generate_category_index(
    config: &ProcessorConfig,
    content_type: &str,
    language: &str,
    category: &str,
    posts: &[PostEntry],
) -> Result<()> {
    let category_index_path = config
        .static_output_path
        .join(content_type)
        .join(language)
        .join(category)
        .join("index.json");

    let category_index = ContentIndex {
        generated_at: chrono::Utc::now().to_rfc3339(),
        content_type: content_type.to_string(),
        language: language.to_string(),
        posts: posts.to_vec(),
        total: posts.len(),
    };

    let index_json = serde_json::to_string_pretty(&category_index)?;
    fs::write(&category_index_path, index_json)?;

    println!(
        "  📂 Generated category index: {}/{}/index.json with {} posts",
        content_type,
        category,
        posts.len()
    );

    Ok(())
}

/// Read configuration files for filtering
fn read_filter_config(
    content_source: &Path,
) -> Result<(Vec<String>, Vec<String>, Option<serde_json::Value>)> {
    let mut available_categories = Vec::new();
    let mut available_tags = Vec::new();
    let mut info_data = None;

    // Read list_categories.toml
    let categories_file = content_source.join("list_categories.toml");
    if categories_file.exists() {
        let content = fs::read_to_string(&categories_file)?;
        if let Ok(toml_value) = toml::from_str::<toml::Value>(&content) {
            if let Some(categories) = toml_value.get("categories").and_then(|v| v.as_array()) {
                for cat in categories {
                    if let Some(name) = cat.get("name").and_then(|v| v.as_str()) {
                        if cat.get("enabled").and_then(|v| v.as_bool()).unwrap_or(true) {
                            available_categories.push(name.to_string());
                        }
                    }
                }
            }
        }
    }

    // Read list_tags.toml
    let tags_file = content_source.join("list_tags.toml");
    if tags_file.exists() {
        let content = fs::read_to_string(&tags_file)?;
        if let Ok(toml_value) = toml::from_str::<toml::Value>(&content) {
            if let Some(tags) = toml_value.get("tags").and_then(|v| v.as_array()) {
                for tag in tags {
                    if let Some(name) = tag.get("name").and_then(|v| v.as_str()) {
                        if tag.get("enabled").and_then(|v| v.as_bool()).unwrap_or(true) {
                            available_tags.push(name.to_string());
                        }
                    }
                }
            }
        }
    }

    // Read info.toml
    let info_file = content_source.join("info.toml");
    if info_file.exists() {
        let content = fs::read_to_string(&info_file)?;
        if let Ok(toml_value) = toml::from_str::<toml::Value>(&content) {
            // Convert TOML to JSON for storage
            info_data = Some(serde_json::to_value(toml_value)?);
        }
    }

    Ok((available_categories, available_tags, info_data))
}

/// Get default emoji for category based on known mappings
fn get_category_emoji(category: &str) -> String {
    let emojis = HashMap::from([
        ("architecture", "🏛️"),
        ("devops", "🛠️"),
        ("infrastructure", "🏗️"),
        ("rust", "🦀"),
        ("web3", "⛓️"),
        ("blockchain", "💎"),
        ("web-development", "🌐"),
        ("docker", "🐳"),
        ("kubernetes", "☸️"),
        ("ci-cd", "🔄"),
        ("async-programming", "🌊"),
        ("rust-programming", "🦀"),
        ("frontend", "💻"),
        ("backend", "⚙️"),
        ("programacion-asincrona", "🌊"),
        ("programacion-rust", "🦀"),
        ("cicd", "🔄"),
    ]);

    emojis.get(category).unwrap_or(&"📂").to_string()
}

/// Get default emoji for tag based on known mappings
fn get_tag_emoji(tag: &str) -> String {
    let emojis = HashMap::from([
        ("rust", "🦀"),
        ("leptos", "⚡"),
        ("axum", "🔧"),
        ("web-development", "🌐"),
        ("architecture", "🏛️"),
        ("devops", "🛠️"),
        ("infrastructure", "🏗️"),
        ("docker", "🐳"),
        ("kubernetes", "☸️"),
        ("ci-cd", "🔄"),
        ("blockchain", "💎"),
        ("web3", "⛓️"),
        ("microservices", "🔗"),
        ("self-hosted", "🏠"),
        ("patterns", "📐"),
        ("performance", "⚡"),
        ("testing", "🧪"),
        ("security", "🔒"),
        ("monitoring", "📊"),
        ("deployment", "🚀"),
        ("api", "🔌"),
        ("database", "🗄️"),
        ("frontend", "💻"),
        ("backend", "⚙️"),
        ("terraform", "🌍"),
        ("error-handling", "🛡️"),
        ("async", "⚡"),
        ("syntax", "🎨"),
        ("highlighting", "🖍️"),
        ("smart-contracts", "📜"),
        ("iac", "🏭"),
        ("gitlab", "🦊"),
        ("async-programming", "🌊"),
        ("rust-programming", "🦀"),
        ("optimization", "💡"),
        ("best-practices", "⭐"),
        ("cdci", "🔄"),
    ]);

    emojis.get(tag).unwrap_or(&"🏷️").to_string()
}

/// Generate filter-index.json for a specific content type and language
fn generate_filter_index(
    config: &ProcessorConfig,
    content_type: &str,
    language: &str,
    posts: &[PostEntry],
) -> Result<()> {
    // Read filter configuration
    let (available_categories, available_tags, info_data) =
        read_filter_config(&config.content_source_path)?;

    // Collect actually used categories and tags from posts
    let mut used_categories = std::collections::HashSet::new();
    let mut used_tags = std::collections::HashSet::new();

    for post in posts {
        if let Some(ref category) = post.frontmatter.category {
            if available_categories.is_empty() || available_categories.contains(category) {
                used_categories.insert(category.clone());
            }
        }

        if let Some(ref tags) = post.frontmatter.tags {
            for tag in tags {
                if available_tags.is_empty() || available_tags.contains(tag) {
                    used_tags.insert(tag.clone());
                }
            }
        }
    }

    // Create category data with emojis
    let mut categories = HashMap::new();
    for category in used_categories {
        categories.insert(
            category.clone(),
            CategoryData {
                emoji: get_category_emoji(&category),
            },
        );
    }

    // Create tag data with emojis
    let mut tags = HashMap::new();
    for tag in used_tags {
        tags.insert(
            tag.clone(),
            TagData {
                emoji: get_tag_emoji(&tag),
            },
        );
    }

    // Create filter index
    let filter_index = FilterIndex {
        generated_at: chrono::Utc::now().to_rfc3339(),
        content_type: content_type.to_string(),
        language: language.to_string(),
        total_posts: posts.len(),
        info: info_data,
        tags,
        categories,
    };

    // Write filter-index.json
    let filter_index_path = config
        .static_output_path
        .join(content_type)
        .join(language)
        .join("filter-index.json");

    let filter_json = serde_json::to_string_pretty(&filter_index)?;
    fs::write(&filter_index_path, filter_json)?;

    println!(
        "  🏷️  Generated filter-index.json with {} tags and {} categories",
        filter_index.tags.len(),
        filter_index.categories.len()
    );

    Ok(())
}

/// Generate content-manifest.json at the root level
fn generate_content_manifest(config: &ProcessorConfig) -> Result<()> {
    let mut structure = HashMap::new();

    // Scan generated content to build structure
    for content_type in &config.enabled_content_types {
        let mut content_type_structure = HashMap::new();

        for language in &config.enabled_languages {
            let content_dir = config.static_output_path.join(content_type).join(language);

            if content_dir.exists() {
                // Count files
                let html_count = glob(&format!("{}/**/*.html", content_dir.display()))
                    .map(|paths| paths.count())
                    .unwrap_or(0);

                let markdown_count = glob(&format!(
                    "{}/../../../content/{}/{}/**/*.md",
                    content_dir.display(),
                    content_type,
                    language
                ))
                .map(|paths| paths.count())
                .unwrap_or(0);

                let has_index = content_dir.join("index.json").exists();

                content_type_structure.insert(
                    language.clone(),
                    ContentTypeInfo {
                        html_count,
                        markdown_count,
                        has_index,
                    },
                );
            }
        }

        if !content_type_structure.is_empty() {
            structure.insert(content_type.clone(), content_type_structure);
        }
    }

    // Create manifest
    let manifest = ContentManifest {
        generated_at: chrono::Utc::now().to_rfc3339(),
        version: "1.0".to_string(),
        languages: config.enabled_languages.clone(),
        content_types: config.enabled_content_types.clone(),
        content_root_path: config.content_source_path.display().to_string(),
        build_info: BuildInfo {
            source_format: "markdown".to_string(),
            output_format: "html".to_string(),
            converter: "rustelo_content_processor".to_string(),
            features: vec![
                "syntax-highlighting".to_string(),
                "css-classes".to_string(),
                "localization".to_string(),
                "filter-indexing".to_string(),
            ],
        },
        structure,
    };

    // Write content-manifest.json at root
    let manifest_path = config.static_output_path.join("content-manifest.json");
    let manifest_json = serde_json::to_string_pretty(&manifest)?;
    fs::write(&manifest_path, manifest_json)?;

    println!("📋 Generated content-manifest.json");

    Ok(())
}

/// Generate category-specific indexes for all posts (grouped by category)
fn generate_category_indexes_for_posts(
    config: &ProcessorConfig,
    content_type: &str,
    language: &str,
    posts: &[PostEntry],
) -> Result<()> {
    use std::collections::HashMap;

    // Group posts by category
    let mut category_posts: HashMap<String, Vec<&PostEntry>> = HashMap::new();

    for post in posts {
        if let Some(ref category) = post.frontmatter.category {
            category_posts
                .entry(category.clone())
                .or_insert_with(Vec::new)
                .push(post);
        }
    }

    // Generate index.json for each category
    for (category, category_post_refs) in category_posts {
        let category_posts_owned: Vec<PostEntry> =
            category_post_refs.into_iter().cloned().collect();

        // Ensure category directory exists
        let category_dir = config
            .static_output_path
            .join(content_type)
            .join(language)
            .join(&category);
        fs::create_dir_all(&category_dir)?;

        // Generate category index with full frontmatter
        generate_category_index(
            config,
            content_type,
            language,
            &category,
            &category_posts_owned,
        )?;
    }

    Ok(())
}

/// Main processing function
fn process_all_content(config: &ProcessorConfig) -> Result<()> {
    println!("🚀 Starting content processing...");
    println!("📁 Source: {}", config.content_source_path.display());
    println!("📂 Output: {}", config.static_output_path.display());

    for content_type in &config.enabled_content_types {
        for language in &config.enabled_languages {
            process_content_type_language(config, content_type, language)?;
        }
    }

    // Generate content-manifest.json at the root
    generate_content_manifest(config)?;

    println!("🎉 Content processing complete!");
    Ok(())
}

fn main() -> Result<()> {
    dotenv::dotenv().ok();

    let matches = Command::new("content-processor")
        .about("Process markdown content to HTML/JSON with index generation")
        .arg(
            Arg::new("watch")
                .long("watch")
                .short('w')
                .action(clap::ArgAction::SetTrue)
                .help("Watch for file changes and auto-regenerate"),
        )
        .arg(
            Arg::new("content-type")
                .long("content-type")
                .short('t')
                .value_name("TYPE")
                .help("Process specific content type only"),
        )
        .arg(
            Arg::new("language")
                .long("language")
                .short('l')
                .value_name("LANG")
                .help("Process specific language only"),
        )
        .arg(
            Arg::new("category")
                .long("category")
                .short('c')
                .value_name("CATEGORY")
                .help("Process specific category only (subdirectory within content-type/language)"),
        )
        .arg(
            Arg::new("file")
                .long("file")
                .short('f')
                .value_name("FILE")
                .help("Process specific markdown file only (supports glob patterns)"),
        )
        .get_matches();

    let mut config = ProcessorConfig::from_env()?;
    config.watch_mode = matches.get_flag("watch");

    // Filter by specific content type if specified
    if let Some(content_type) = matches.get_one::<String>("content-type") {
        config.enabled_content_types.retain(|t| t == content_type);
    }

    // Filter by specific language if specified
    if let Some(language) = matches.get_one::<String>("language") {
        config.enabled_languages.retain(|l| l == language);
    }

    // Handle specific file processing
    if let Some(file_pattern) = matches.get_one::<String>("file") {
        return process_specific_files(&config, file_pattern);
    }

    // Handle category-specific processing
    if let Some(category) = matches.get_one::<String>("category") {
        return process_specific_category(&config, category);
    }

    if config.watch_mode {
        println!("👀 Watch mode enabled - monitoring for changes...");
        // TODO: Implement file watching with notify crate
        println!("⚠️  Watch mode not yet implemented, running once");
    }

    process_all_content(&config)
}
