//! MDX parser for .agent.mdx files
//!
//! Parses MDX with @ directives:
//! - @agent { ... }
//! - @input name: Type
//! - @import "path" as alias
//! - @shell "command" as alias
//! - @if condition { ... }
//! - @validate output { ... }

use nom::{
    branch::alt,
    bytes::complete::{tag, take_until, take_while1},
    character::complete::{char, line_ending, multispace0, multispace1, not_line_ending},
    combinator::map,
    multi::many0,
    sequence::{delimited, terminated},
    IResult, Parser,
};

use super::ast::*;
use super::directives::AgentDirective;

/// Parse frontmatter delimited by ---
pub fn parse_frontmatter(input: &str) -> IResult<&str, Vec<AgentDirective>> {
    let (input, _) = tag("---").parse(input)?;
    let (input, _) = line_ending.parse(input)?;
    let (input, directives) = many0(terminated(parse_directive, multispace0)).parse(input)?;
    let (input, _) = tag("---").parse(input)?;
    let (input, _) = line_ending.parse(input)?;
    Ok((input, directives))
}

/// Parse any @ directive
fn parse_directive(input: &str) -> IResult<&str, AgentDirective> {
    alt((
        map(parse_agent_directive, AgentDirective::Agent),
        map(parse_input_directive, AgentDirective::Input),
        map(parse_import_directive, AgentDirective::Import),
        map(parse_shell_directive, AgentDirective::Shell),
        map(parse_validate_directive, AgentDirective::Validate),
    ))
    .parse(input)
}

/// Parse @agent { role: architect, llm: claude-opus-4, tools: [...] }
fn parse_agent_directive(input: &str) -> IResult<&str, AgentConfig> {
    let (input, _) = tag("@agent").parse(input)?;
    let (input, _) = multispace0.parse(input)?;
    let (input, content) = delimited(char('{'), take_until("}"), char('}')).parse(input)?;

    let config = parse_agent_config(content);
    Ok((input, config))
}

/// Parse agent config fields (simplified - not full Nickel parser)
fn parse_agent_config(input: &str) -> AgentConfig {
    let mut role = String::new();
    let mut llm = String::new();
    let mut tools = Vec::new();

    for line in input.lines() {
        let line = line.trim();
        if line.starts_with("role:") {
            role = line
                .trim_start_matches("role:")
                .trim()
                .trim_end_matches(',')
                .trim()
                .to_string();
        } else if line.starts_with("llm:") {
            llm = line
                .trim_start_matches("llm:")
                .trim()
                .trim_end_matches(',')
                .trim()
                .to_string();
        } else if line.starts_with("tools:") {
            let tools_str = line
                .trim_start_matches("tools:")
                .trim()
                .trim_end_matches(',');
            if let Some(stripped) = tools_str
                .strip_prefix('[')
                .and_then(|s| s.strip_suffix(']'))
            {
                tools = stripped.split(',').map(|s| s.trim().to_string()).collect();
            }
        }
    }

    AgentConfig { role, llm, tools }
}

/// Parse @input name: Type or @input name?: Type (optional)
fn parse_input_directive(input: &str) -> IResult<&str, InputDecl> {
    let (input, _) = tag("@input").parse(input)?;
    let (input, _) = multispace1.parse(input)?;

    let (input, name_part) =
        take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '?').parse(input)?;
    let (input, _) = char(':').parse(input)?;
    let (input, _) = multispace0.parse(input)?;
    let (input, type_spec) = not_line_ending.parse(input)?;

    let (name, optional) = if let Some(n) = name_part.strip_suffix('?') {
        (n.to_string(), true)
    } else {
        (name_part.to_string(), false)
    };

    Ok((
        input,
        InputDecl {
            name,
            type_spec: type_spec.trim().to_string(),
            optional,
        },
    ))
}

/// Parse @import "path" as alias
fn parse_import_directive(input: &str) -> IResult<&str, ImportDecl> {
    let (input, _) = tag("@import").parse(input)?;
    let (input, _) = multispace1.parse(input)?;
    let (input, path) = delimited(char('"'), take_until("\""), char('"')).parse(input)?;
    let (input, _) = multispace1.parse(input)?;
    let (input, _) = tag("as").parse(input)?;
    let (input, _) = multispace1.parse(input)?;
    let (input, alias) = take_while1(|c: char| c.is_alphanumeric() || c == '_').parse(input)?;

    Ok((
        input,
        ImportDecl {
            path: path.to_string(),
            alias: alias.to_string(),
        },
    ))
}

/// Parse @shell "command" as alias
fn parse_shell_directive(input: &str) -> IResult<&str, ShellDecl> {
    let (input, _) = tag("@shell").parse(input)?;
    let (input, _) = multispace1.parse(input)?;
    let (input, command) = delimited(char('"'), take_until("\""), char('"')).parse(input)?;
    let (input, _) = multispace1.parse(input)?;
    let (input, _) = tag("as").parse(input)?;
    let (input, _) = multispace1.parse(input)?;
    let (input, alias) = take_while1(|c: char| c.is_alphanumeric() || c == '_').parse(input)?;

    Ok((
        input,
        ShellDecl {
            command: command.to_string(),
            alias: alias.to_string(),
        },
    ))
}

/// Parse @validate output { must_contain: [...], format: markdown }
fn parse_validate_directive(input: &str) -> IResult<&str, ValidationRules> {
    let (input, _) = tag("@validate").parse(input)?;
    let (input, _) = multispace1.parse(input)?;
    let (input, _) = tag("output").parse(input)?;
    let (input, _) = multispace0.parse(input)?;
    let (input, content) = delimited(char('{'), take_until("}"), char('}')).parse(input)?;

    let validation = parse_validation_rules(content);
    Ok((input, validation))
}

/// Parse validation rules (simplified)
fn parse_validation_rules(input: &str) -> ValidationRules {
    let mut must_contain = Vec::new();
    let mut format = String::from("markdown");

    for line in input.lines() {
        let line = line.trim();
        if line.starts_with("must_contain:") {
            let list_str = line.trim_start_matches("must_contain:").trim();
            if let Some(stripped) = list_str.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
                must_contain = stripped
                    .split(',')
                    .map(|s| s.trim().trim_matches('"').to_string())
                    .collect();
            }
        } else if line.starts_with("format:") {
            format = line
                .trim_start_matches("format:")
                .trim()
                .trim_end_matches(',')
                .trim()
                .to_string();
        }
    }

    ValidationRules {
        must_contain,
        format,
    }
}

/// Parse template body with {{variable}} interpolation
pub fn parse_template_body(input: &str) -> IResult<&str, Vec<MarkupNode>> {
    many0(alt((
        map(parse_variable, MarkupNode::Variable),
        map(take_while1(|c| c != '{'), |s: &str| {
            MarkupNode::Text(s.to_string())
        }),
    )))
    .parse(input)
}

/// Parse {{variable}} or {{#if condition}}...{{/if}}
fn parse_variable(input: &str) -> IResult<&str, String> {
    let (input, _) = tag("{{").parse(input)?;
    let (input, content) = take_until("}}").parse(input)?;
    let (input, _) = tag("}}").parse(input)?;
    Ok((input, content.trim().to_string()))
}

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

    #[test]
    fn test_parse_input_directive() {
        let input = "@input feature_name: String";
        let (_, decl) = parse_input_directive(input).unwrap();
        assert_eq!(decl.name, "feature_name");
        assert_eq!(decl.type_spec, "String");
        assert!(!decl.optional);
    }

    #[test]
    fn test_parse_optional_input() {
        let input = "@input requirements?: String";
        let (_, decl) = parse_input_directive(input).unwrap();
        assert_eq!(decl.name, "requirements");
        assert!(decl.optional);
    }

    #[test]
    fn test_parse_import_directive() {
        let input = r#"@import "./docs/**/*.md" as arch_docs"#;
        let (_, decl) = parse_import_directive(input).unwrap();
        assert_eq!(decl.path, "./docs/**/*.md");
        assert_eq!(decl.alias, "arch_docs");
    }

    #[test]
    fn test_parse_shell_directive() {
        let input = r#"@shell "git log --oneline -20" as recent_commits"#;
        let (_, decl) = parse_shell_directive(input).unwrap();
        assert_eq!(decl.command, "git log --oneline -20");
        assert_eq!(decl.alias, "recent_commits");
    }

    #[test]
    fn test_parse_variable() {
        let input = "{{feature_name}}";
        let (_, var) = parse_variable(input).unwrap();
        assert_eq!(var, "feature_name");
    }
}
