//! Markdown parser with Logseq compatibility
//!
//! This module parses KOGRAL documents:
//! - YAML frontmatter extraction
//! - Markdown body parsing
//! - Wikilink resolution ([[links]])
//! - Code reference parsing (@path/to/file.rs:42)

use std::collections::HashMap;
use std::fmt::Write;

use chrono::{DateTime, Utc};
use serde_yaml;

use crate::error::{KbError, Result};
use crate::models::{Node, NodeStatus, NodeType};
use crate::regex_patterns::{CODE_REF_PATTERN, WIKILINK_PATTERN};

/// Extract frontmatter as a map of key-value strings
///
/// Parses YAML frontmatter and returns it as a `HashMap<String, String>`.
/// This is useful for guideline inheritance and other metadata extraction.
///
/// # Errors
///
/// Returns an error if YAML frontmatter is malformed
pub fn parse_frontmatter_map(content: &str) -> Result<HashMap<String, String>> {
    let (frontmatter_str, _body) = extract_frontmatter(content)?;

    let metadata = serde_yaml::from_str::<serde_yaml::Value>(&frontmatter_str)
        .map_err(|e| KbError::Parser(format!("Invalid YAML: {e}")))?;

    let mut result = HashMap::new();

    if let serde_yaml::Value::Mapping(map) = metadata {
        for (key, value) in map {
            if let (serde_yaml::Value::String(k), serde_yaml::Value::String(v)) = (key, value) {
                result.insert(k, v);
            }
        }
    }

    Ok(result)
}

/// Parse a markdown document with YAML frontmatter
///
/// Format:
/// ```markdown
/// ---
/// id: node-123
/// type: note
/// title: Example
/// ---
///
/// # Content here
/// ```
///
/// # Errors
///
/// Returns an error if:
/// - Document is missing frontmatter delimiters (`---`)
/// - YAML frontmatter is malformed or contains invalid syntax
/// - Required fields (`id`, `type`, `title`) are missing or invalid
pub fn parse_document(content: &str) -> Result<Node> {
    let (frontmatter, body) = extract_frontmatter(content)?;
    let metadata = parse_frontmatter(&frontmatter)?;

    // Build node from metadata
    let mut node = Node::new(metadata.node_type, metadata.title.clone());

    node.id = metadata.id;
    node.created = metadata.created;
    node.modified = metadata.modified;
    node.status = metadata.status;
    node.tags = metadata.tags;
    node.content.clone_from(&body);
    node.relates_to = metadata.relates_to;
    node.depends_on = metadata.depends_on;
    node.implements = metadata.implements;
    node.extends = metadata.extends;
    node.project = metadata.project;
    node.metadata = metadata.extra;

    Ok(node)
}

/// Frontmatter metadata
#[derive(Debug, serde::Deserialize)]
struct FrontmatterMetadata {
    id: String,
    #[serde(rename = "type")]
    node_type: NodeType,
    title: String,
    created: DateTime<Utc>,
    modified: DateTime<Utc>,
    #[serde(default)]
    tags: Vec<String>,
    #[serde(default)]
    status: NodeStatus,
    #[serde(default)]
    relates_to: Vec<String>,
    #[serde(default)]
    depends_on: Vec<String>,
    #[serde(default)]
    implements: Vec<String>,
    #[serde(default)]
    extends: Vec<String>,
    #[serde(default)]
    project: Option<String>,
    #[serde(flatten)]
    extra: HashMap<String, serde_json::Value>,
}

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

    if lines.is_empty() || !lines[0].trim().starts_with("---") {
        return Err(KbError::Frontmatter("No frontmatter found".to_string()));
    }

    // Find end of frontmatter
    let end = lines[1..]
        .iter()
        .position(|line| line.trim() == "---")
        .ok_or_else(|| KbError::Frontmatter("Unterminated frontmatter".to_string()))?;

    let frontmatter = lines[1..=end].join("\n");
    let body = lines[end + 2..].join("\n");

    Ok((frontmatter, body))
}

/// Parse YAML frontmatter
fn parse_frontmatter(yaml: &str) -> Result<FrontmatterMetadata> {
    serde_yaml::from_str(yaml)
        .map_err(|e| KbError::Frontmatter(format!("Invalid frontmatter YAML: {e}")))
}

/// Extract wikilinks from markdown content
///
/// Matches: [[link]], [[link|display text]]
///
/// # Panics
///
/// Panics if the hardcoded regex pattern is invalid (should never happen).
#[must_use]
pub fn extract_wikilinks(content: &str) -> Vec<String> {
    WIKILINK_PATTERN
        .captures_iter(content)
        .map(|cap| cap[1].to_string())
        .collect()
}

/// Extract code references from markdown
///
/// Matches: @path/to/file.rs:42
#[must_use]
pub fn extract_code_refs(content: &str) -> Vec<String> {
    CODE_REF_PATTERN
        .captures_iter(content)
        .map(|cap| cap[1].to_string())
        .collect()
}

/// Generate markdown from a node
#[must_use]
pub fn generate_markdown(node: &Node) -> String {
    let mut output = String::new();

    // Frontmatter
    output.push_str("---\n");
    let _ = writeln!(output, "id: {}", node.id);
    let _ = writeln!(output, "type: {}", node.node_type);
    let _ = writeln!(output, "title: {}", node.title);
    let _ = writeln!(output, "created: {}", node.created.to_rfc3339());
    let _ = writeln!(output, "modified: {}", node.modified.to_rfc3339());

    if !node.tags.is_empty() {
        let tags_str = node
            .tags
            .iter()
            .map(|t| format!("\"{t}\""))
            .collect::<Vec<_>>()
            .join(", ");
        let _ = writeln!(output, "tags: [{tags_str}]");
    }

    let _ = writeln!(output, "status: {}", node.status);

    if !node.relates_to.is_empty() {
        output.push_str("relates_to:\n");
        for rel in &node.relates_to {
            let _ = writeln!(output, "  - {rel}");
        }
    }

    if !node.depends_on.is_empty() {
        output.push_str("depends_on:\n");
        for dep in &node.depends_on {
            let _ = writeln!(output, "  - {dep}");
        }
    }

    if let Some(ref project) = node.project {
        let _ = writeln!(output, "project: {project}");
    }

    output.push_str("---\n\n");

    // Body
    output.push_str(&node.content);

    output
}

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

    #[test]
    fn test_extract_frontmatter() {
        let content = r#"---
id: test-123
type: note
---
Content here"#;

        let (fm, body) = extract_frontmatter(content).unwrap();
        assert!(fm.contains("id: test-123"));
        assert_eq!(body.trim(), "Content here");
    }

    #[test]
    fn test_extract_wikilinks() {
        let content = "This is [[link1]] and [[link2|Display Text]].";
        let links = extract_wikilinks(content);
        assert_eq!(links, vec!["link1", "link2"]);
    }

    #[test]
    fn test_extract_code_refs() {
        let content = "See @src/main.rs:42 and @lib/util.rs:100";
        let refs = extract_code_refs(content);
        assert_eq!(refs, vec!["src/main.rs:42", "lib/util.rs:100"]);
    }
}
