use nyash_rust::ASTNode;
use std::sync::{Mutex, OnceLock};

/// MacroBox API — user-extensible macro expansion units (experimental)
///
/// Philosophy:
/// - Deterministic, side-effect free, no IO. Pure AST -> AST transforms.
/// - Prefer operating on public interfaces (Box public fields/methods) and avoid
///   coupling to private internals.
pub trait MacroBox: Send + Sync {
    fn name(&self) -> &'static str;
    fn expand(&self, ast: &ASTNode) -> ASTNode;
}

static REG: OnceLock<Mutex<Vec<&'static dyn MacroBox>>> = OnceLock::new();

fn registry() -> &'static Mutex<Vec<&'static dyn MacroBox>> {
    REG.get_or_init(|| Mutex::new(Vec::new()))
}

/// Register a MacroBox. Intended to be called at startup/init paths.
pub fn register(m: &'static dyn MacroBox) {
    let reg = registry();
    let mut guard = reg.lock().expect("macro registry poisoned");
    // avoid duplicates
    if !guard.iter().any(|e| e.name() == m.name()) {
        guard.push(m);
    }
}

/// Gate for MacroBox execution.
///
/// Legacy env `NYASH_MACRO_BOX=1` still forces ON, but by default we
/// synchronize with the macro system gate so user macros run when macros are enabled.
pub fn enabled() -> bool {
    if crate::config::env::macro_box() {
        return true;
    }
    super::enabled()
}

/// Expand AST by applying all registered MacroBoxes in order once.
pub fn expand_all_once(ast: &ASTNode) -> ASTNode {
    if !enabled() {
        return ast.clone();
    }
    let reg = registry();
    let guard = reg.lock().expect("macro registry poisoned");
    let mut cur = ast.clone();
    for m in guard.iter() {
        let out = m.expand(&cur);
        cur = out;
    }
    cur
}

// ---- Built-in example (optional) ----

pub struct UppercasePrintMacro;

impl MacroBox for UppercasePrintMacro {
    fn name(&self) -> &'static str {
        "UppercasePrintMacro"
    }
    fn expand(&self, ast: &ASTNode) -> ASTNode {
        use nyash_rust::ast::{ASTNode as A, LiteralValue, Span};
        fn go(n: &A) -> A {
            match n.clone() {
                A::Program { statements, span } => A::Program {
                    statements: statements.into_iter().map(|c| go(&c)).collect(),
                    span,
                },
                A::Print { expression, span } => {
                    match &*expression {
                        A::Literal {
                            value: LiteralValue::String(s),
                            ..
                        } => {
                            // Demo: if string starts with "UPPER:", uppercase the rest.
                            if let Some(rest) = s.strip_prefix("UPPER:") {
                                let up = rest.to_uppercase();
                                A::Print {
                                    expression: Box::new(A::Literal {
                                        value: LiteralValue::String(up),
                                        span: Span::unknown(),
                                    }),
                                    span,
                                }
                            } else {
                                A::Print {
                                    expression: Box::new(go(&*expression)),
                                    span,
                                }
                            }
                        }
                        other => A::Print {
                            expression: Box::new(go(other)),
                            span,
                        },
                    }
                }
                A::Assignment {
                    target,
                    value,
                    span,
                } => A::Assignment {
                    target: Box::new(go(&*target)),
                    value: Box::new(go(&*value)),
                    span,
                },
                A::If {
                    condition,
                    then_body,
                    else_body,
                    span,
                } => A::If {
                    condition: Box::new(go(&*condition)),
                    then_body: then_body.into_iter().map(|c| go(&c)).collect(),
                    else_body: else_body.map(|v| v.into_iter().map(|c| go(&c)).collect()),
                    span,
                },
                A::Return { value, span } => A::Return {
                    value: value.as_ref().map(|v| Box::new(go(v))),
                    span,
                },
                A::FieldAccess {
                    object,
                    field,
                    span,
                } => A::FieldAccess {
                    object: Box::new(go(&*object)),
                    field,
                    span,
                },
                A::MethodCall {
                    object,
                    method,
                    arguments,
                    span,
                } => A::MethodCall {
                    object: Box::new(go(&*object)),
                    method,
                    arguments: arguments.into_iter().map(|c| go(&c)).collect(),
                    span,
                },
                A::FunctionCall {
                    name,
                    arguments,
                    span,
                } => A::FunctionCall {
                    name,
                    arguments: arguments.into_iter().map(|c| go(&c)).collect(),
                    span,
                },
                A::BinaryOp {
                    operator,
                    left,
                    right,
                    span,
                } => A::BinaryOp {
                    operator,
                    left: Box::new(go(&*left)),
                    right: Box::new(go(&*right)),
                    span,
                },
                A::UnaryOp {
                    operator,
                    operand,
                    span,
                } => A::UnaryOp {
                    operator,
                    operand: Box::new(go(&*operand)),
                    span,
                },
                A::ArrayLiteral { elements, span } => A::ArrayLiteral {
                    elements: elements.into_iter().map(|c| go(&c)).collect(),
                    span,
                },
                A::MapLiteral { entries, span } => A::MapLiteral {
                    entries: entries.into_iter().map(|(k, v)| (k, go(&v))).collect(),
                    span,
                },
                other => other,
            }
        }
        go(ast)
    }
}

static INIT_FLAG: OnceLock<()> = OnceLock::new();

/// Initialize built-in demo MacroBoxes when enabled by env flags.
pub fn init_builtin() {
    INIT_FLAG.get_or_init(|| {
        // Explicit example toggle
        if crate::config::env::macro_box_example() {
            register(&UppercasePrintMacro);
        }
        // Comma-separated names: NYASH_MACRO_BOX_ENABLE="UppercasePrintMacro,Other"
        if let Some(list) = crate::config::env::macro_box_enable() {
            for name in list.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
                match name {
                    "UppercasePrintMacro" => register(&UppercasePrintMacro),
                    _ => {
                        eprintln!("[macro][box] unknown MacroBox '{}', ignoring", name);
                    }
                }
            }
        }
    });
}
