//! Transpiler: AST → Nickel code generator
//!
//! Converts parsed MDX AST into valid Nickel configuration code

use crate::error::{Error, Result};
use crate::parser::ast::{
    AgentConfig, ImportDecl, InputDecl, MarkupAst, MarkupNode, ShellDecl, ValidationRules,
};
use std::fmt::Write;

/// Transpiles MDX AST to Nickel code
pub struct NickelTranspiler;

impl NickelTranspiler {
    /// Create a new Nickel transpiler
    pub fn new() -> Self {
        Self
    }

    /// Transpile AST to Nickel code string
    pub fn transpile(&self, ast: &MarkupAst) -> Result<String> {
        let mut nickel_code = String::new();

        // Extract components from AST
        let mut agent_config: Option<&AgentConfig> = None;
        let mut inputs: Vec<&InputDecl> = Vec::new();
        let mut imports: Vec<&ImportDecl> = Vec::new();
        let mut shells: Vec<&ShellDecl> = Vec::new();
        let mut validation: Option<&ValidationRules> = None;
        let mut template_parts: Vec<String> = Vec::new();

        for node in &ast.nodes {
            match node {
                MarkupNode::Agent(config) => agent_config = Some(config),
                MarkupNode::Input(input) => inputs.push(input),
                MarkupNode::Import(import) => imports.push(import),
                MarkupNode::Shell(shell) => shells.push(shell),
                MarkupNode::Validate(val) => validation = Some(val),
                MarkupNode::Variable(var) => template_parts.push(format!("{{{{{}}}}}", var)),
                MarkupNode::Text(text) => template_parts.push(text.clone()),
                MarkupNode::If(_) => {
                    // TODO: Handle if blocks in template
                }
            }
        }

        // Start Nickel record
        writeln!(&mut nickel_code, "{{")
            .map_err(|e| Error::transpile("Failed to write Nickel code", e.to_string()))?;

        // Generate config section
        if let Some(config) = agent_config {
            self.write_config(&mut nickel_code, config)?;
        } else {
            return Err(Error::transpile(
                "Missing @agent directive",
                "agent config required",
            ));
        }

        // Generate inputs section (empty record - inputs are provided at runtime)
        writeln!(&mut nickel_code, "  inputs = {{}},")
            .map_err(|e| Error::transpile("Failed to write inputs", e.to_string()))?;

        // Generate context section (imports + shell commands)
        if !imports.is_empty() || !shells.is_empty() {
            self.write_context(&mut nickel_code, &imports, &shells)?;
        }

        // Generate validation section
        if let Some(val) = validation {
            self.write_validation(&mut nickel_code, val)?;
        }

        // Generate template section
        let template = template_parts.join("");
        self.write_template(&mut nickel_code, &template)?;

        // Close Nickel record
        writeln!(&mut nickel_code, "}}")
            .map_err(|e| Error::transpile("Failed to close Nickel record", e.to_string()))?;

        Ok(nickel_code)
    }

    /// Write config section
    fn write_config(&self, code: &mut String, config: &AgentConfig) -> Result<()> {
        writeln!(code, "  config = {{")
            .map_err(|e| Error::transpile("Failed to write config", e.to_string()))?;

        writeln!(code, "    role = \"{}\",", config.role)
            .map_err(|e| Error::transpile("Failed to write role", e.to_string()))?;

        writeln!(code, "    llm = \"{}\",", config.llm)
            .map_err(|e| Error::transpile("Failed to write llm", e.to_string()))?;

        if !config.tools.is_empty() {
            write!(code, "    tools = [")
                .map_err(|e| Error::transpile("Failed to write tools", e.to_string()))?;
            for (i, tool) in config.tools.iter().enumerate() {
                if i > 0 {
                    write!(code, ", ").map_err(|e| {
                        Error::transpile("Failed to write tool separator", e.to_string())
                    })?;
                }
                write!(code, "\"{}\"", tool)
                    .map_err(|e| Error::transpile("Failed to write tool", e.to_string()))?;
            }
            writeln!(code, "],")
                .map_err(|e| Error::transpile("Failed to close tools", e.to_string()))?;
        }

        writeln!(code, "  }},")
            .map_err(|e| Error::transpile("Failed to close config", e.to_string()))?;

        Ok(())
    }

    /// Write context section
    fn write_context(
        &self,
        code: &mut String,
        imports: &[&ImportDecl],
        shells: &[&ShellDecl],
    ) -> Result<()> {
        writeln!(code, "  context = {{")
            .map_err(|e| Error::transpile("Failed to write context", e.to_string()))?;

        if !imports.is_empty() {
            write!(code, "    imports = [")
                .map_err(|e| Error::transpile("Failed to write imports", e.to_string()))?;
            for (i, import) in imports.iter().enumerate() {
                if i > 0 {
                    write!(code, ", ").map_err(|e| {
                        Error::transpile("Failed to write import separator", e.to_string())
                    })?;
                }
                write!(code, "\"{}\"", import.path)
                    .map_err(|e| Error::transpile("Failed to write import path", e.to_string()))?;
            }
            writeln!(code, "],")
                .map_err(|e| Error::transpile("Failed to close imports", e.to_string()))?;
        }

        if !shells.is_empty() {
            write!(code, "    shell_commands = [")
                .map_err(|e| Error::transpile("Failed to write shell_commands", e.to_string()))?;
            for (i, shell) in shells.iter().enumerate() {
                if i > 0 {
                    write!(code, ", ").map_err(|e| {
                        Error::transpile("Failed to write shell separator", e.to_string())
                    })?;
                }
                write!(code, "\"{}\"", shell.command).map_err(|e| {
                    Error::transpile("Failed to write shell command", e.to_string())
                })?;
            }
            writeln!(code, "],")
                .map_err(|e| Error::transpile("Failed to close shell_commands", e.to_string()))?;
        }

        writeln!(code, "  }},")
            .map_err(|e| Error::transpile("Failed to close context", e.to_string()))?;

        Ok(())
    }

    /// Write validation section
    fn write_validation(&self, code: &mut String, validation: &ValidationRules) -> Result<()> {
        writeln!(code, "  validation = {{")
            .map_err(|e| Error::transpile("Failed to write validation", e.to_string()))?;

        // Always write must_contain array
        write!(code, "    must_contain = [")
            .map_err(|e| Error::transpile("Failed to write must_contain", e.to_string()))?;
        for (i, item) in validation.must_contain.iter().enumerate() {
            if i > 0 {
                write!(code, ", ").map_err(|e| {
                    Error::transpile("Failed to write must_contain separator", e.to_string())
                })?;
            }
            write!(code, "\"{}\"", item.replace('"', "\\\"")).map_err(|e| {
                Error::transpile("Failed to write must_contain item", e.to_string())
            })?;
        }
        writeln!(code, "],")
            .map_err(|e| Error::transpile("Failed to close must_contain", e.to_string()))?;

        // Always write must_not_contain array (default empty)
        writeln!(code, "    must_not_contain = [],")
            .map_err(|e| Error::transpile("Failed to write must_not_contain", e.to_string()))?;

        writeln!(code, "    format = \"{}\",", validation.format)
            .map_err(|e| Error::transpile("Failed to write format", e.to_string()))?;

        writeln!(code, "  }},")
            .map_err(|e| Error::transpile("Failed to close validation", e.to_string()))?;

        Ok(())
    }

    /// Write template section
    fn write_template(&self, code: &mut String, template: &str) -> Result<()> {
        // Escape quotes in template
        let escaped = template.replace('\\', "\\\\").replace('"', "\\\"");

        writeln!(code, "  template = \"{}\",", escaped)
            .map_err(|e| Error::transpile("Failed to write template", e.to_string()))?;

        Ok(())
    }
}

impl Default for NickelTranspiler {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_transpile_basic_agent() {
        let ast = MarkupAst {
            nodes: vec![
                MarkupNode::Agent(AgentConfig {
                    role: "architect".to_string(),
                    llm: "claude-opus-4".to_string(),
                    tools: vec!["analyze_codebase".to_string()],
                }),
                MarkupNode::Input(InputDecl {
                    name: "feature_name".to_string(),
                    type_spec: "String".to_string(),
                    optional: false,
                }),
                MarkupNode::Text("Design: ".to_string()),
                MarkupNode::Variable("feature_name".to_string()),
            ],
        };

        let transpiler = NickelTranspiler::new();
        let result = transpiler.transpile(&ast);

        assert!(result.is_ok());
        let nickel = result.unwrap();
        assert!(nickel.contains("config = {"));
        assert!(nickel.contains("role = \"architect\","));
        assert!(nickel.contains("llm = \"claude-opus-4\","));
        assert!(nickel.contains("inputs = {},"));
        assert!(nickel.contains("template = \"Design: {{feature_name}}\""));
    }

    #[test]
    fn test_transpile_with_validation() {
        let ast = MarkupAst {
            nodes: vec![
                MarkupNode::Agent(AgentConfig {
                    role: "tester".to_string(),
                    llm: "claude-sonnet-4".to_string(),
                    tools: vec![],
                }),
                MarkupNode::Validate(ValidationRules {
                    must_contain: vec!["Test".to_string(), "Assert".to_string()],
                    format: "markdown".to_string(),
                }),
            ],
        };

        let transpiler = NickelTranspiler::new();
        let result = transpiler.transpile(&ast);

        assert!(result.is_ok());
        let nickel = result.unwrap();
        assert!(nickel.contains("validation = {"));
        assert!(nickel.contains("must_contain = [\"Test\", \"Assert\"]"));
        assert!(nickel.contains("format = \"markdown\","));
    }
}
