use nyash_rust::ASTNode;
use std::collections::HashMap;

/// Minimal pattern trait — MVP
pub trait MacroPattern {
    fn match_ast(&self, node: &ASTNode) -> Option<HashMap<String, ASTNode>>;
}

/// Quote/Unquote placeholders — MVP stubs
pub struct AstBuilder;

impl AstBuilder {
    pub fn new() -> Self {
        Self
    }
    pub fn quote(&self, code: &str) -> ASTNode {
        // MVP: parse string into AST using existing parser
        match nyash_rust::parser::NyashParser::parse_from_string(code) {
            Ok(ast) => ast,
            Err(_) => ASTNode::Program {
                statements: vec![],
                span: nyash_rust::ast::Span::unknown(),
            },
        }
    }
    pub fn unquote(&self, template: &ASTNode, _bindings: &HashMap<String, ASTNode>) -> ASTNode {
        // Replace Variables named like "$name" with corresponding bound AST
        fn is_placeholder(name: &str) -> Option<&str> {
            if name.starts_with('$') && name.len() > 1 {
                Some(&name[1..])
            } else {
                None
            }
        }
        fn is_variadic(name: &str) -> Option<&str> {
            if name.starts_with("$...") && name.len() > 4 {
                Some(&name[4..])
            } else {
                None
            }
        }
        fn subst(node: &ASTNode, binds: &HashMap<String, ASTNode>) -> ASTNode {
            match node.clone() {
                ASTNode::Variable { name, .. } => {
                    if let Some(k) = is_placeholder(&name) {
                        if let Some(v) = binds.get(k) {
                            return v.clone();
                        }
                    }
                    node.clone()
                }
                ASTNode::BinaryOp {
                    operator,
                    left,
                    right,
                    span,
                } => ASTNode::BinaryOp {
                    operator,
                    left: Box::new(subst(&left, binds)),
                    right: Box::new(subst(&right, binds)),
                    span,
                },
                ASTNode::UnaryOp {
                    operator,
                    operand,
                    span,
                } => ASTNode::UnaryOp {
                    operator,
                    operand: Box::new(subst(&operand, binds)),
                    span,
                },
                ASTNode::MethodCall {
                    object,
                    method,
                    arguments,
                    span,
                } => {
                    let mut out_args: Vec<ASTNode> = Vec::new();
                    let mut i = 0usize;
                    while i < arguments.len() {
                        if let ASTNode::Variable { name, .. } = &arguments[i] {
                            if let Some(vn) = is_variadic(name) {
                                if let Some(ASTNode::Program { statements, .. }) = binds.get(vn) {
                                    out_args.extend(statements.clone());
                                    i += 1;
                                    continue;
                                }
                            }
                        }
                        out_args.push(subst(&arguments[i], binds));
                        i += 1;
                    }
                    ASTNode::MethodCall {
                        object: Box::new(subst(&object, binds)),
                        method,
                        arguments: out_args,
                        span,
                    }
                }
                ASTNode::FunctionCall {
                    name,
                    arguments,
                    span,
                } => {
                    let mut out_args: Vec<ASTNode> = Vec::new();
                    let mut i = 0usize;
                    while i < arguments.len() {
                        if let ASTNode::Variable { name, .. } = &arguments[i] {
                            if let Some(vn) = is_variadic(name) {
                                if let Some(ASTNode::Program { statements, .. }) = binds.get(vn) {
                                    out_args.extend(statements.clone());
                                    i += 1;
                                    continue;
                                }
                            }
                        }
                        out_args.push(subst(&arguments[i], binds));
                        i += 1;
                    }
                    ASTNode::FunctionCall {
                        name,
                        arguments: out_args,
                        span,
                    }
                }
                ASTNode::ArrayLiteral { elements, span } => {
                    // Splice variadic placeholder inside arrays
                    let mut out_elems: Vec<ASTNode> = Vec::new();
                    let mut i = 0usize;
                    while i < elements.len() {
                        if let ASTNode::Variable { name, .. } = &elements[i] {
                            if let Some(vn) = is_variadic(name) {
                                if let Some(ASTNode::Program { statements, .. }) = binds.get(vn) {
                                    out_elems.extend(statements.clone());
                                    i += 1;
                                    continue;
                                }
                            }
                        }
                        out_elems.push(subst(&elements[i], binds));
                        i += 1;
                    }
                    ASTNode::ArrayLiteral {
                        elements: out_elems,
                        span,
                    }
                }
                ASTNode::MapLiteral { entries, span } => ASTNode::MapLiteral {
                    entries: entries
                        .into_iter()
                        .map(|(k, v)| (k, subst(&v, binds)))
                        .collect(),
                    span,
                },
                ASTNode::FieldAccess {
                    object,
                    field,
                    span,
                } => ASTNode::FieldAccess {
                    object: Box::new(subst(&object, binds)),
                    field,
                    span,
                },
                ASTNode::Assignment {
                    target,
                    value,
                    span,
                } => ASTNode::Assignment {
                    target: Box::new(subst(&target, binds)),
                    value: Box::new(subst(&value, binds)),
                    span,
                },
                ASTNode::Return { value, span } => ASTNode::Return {
                    value: value.as_ref().map(|v| Box::new(subst(v, binds))),
                    span,
                },
                ASTNode::If {
                    condition,
                    then_body,
                    else_body,
                    span,
                } => ASTNode::If {
                    condition: Box::new(subst(&condition, binds)),
                    then_body: then_body.into_iter().map(|n| subst(&n, binds)).collect(),
                    else_body: else_body.map(|v| v.into_iter().map(|n| subst(&n, binds)).collect()),
                    span,
                },
                ASTNode::Program { statements, span } => ASTNode::Program {
                    statements: statements.into_iter().map(|n| subst(&n, binds)).collect(),
                    span,
                },
                other => other,
            }
        }
        subst(template, _bindings)
    }
}

/// Simple template-based pattern that uses Variables named "$name" as bind points.
pub struct TemplatePattern {
    pub template: ASTNode,
}

impl TemplatePattern {
    pub fn new(template: ASTNode) -> Self {
        Self { template }
    }
}

impl MacroPattern for TemplatePattern {
    fn match_ast(&self, node: &ASTNode) -> Option<HashMap<String, ASTNode>> {
        fn is_placeholder(name: &str) -> Option<&str> {
            if name.starts_with('$') && name.len() > 1 {
                Some(&name[1..])
            } else {
                None
            }
        }
        fn is_variadic(name: &str) -> Option<&str> {
            if name.starts_with("$...") && name.len() > 4 {
                Some(&name[4..])
            } else {
                None
            }
        }
        fn go(tpl: &ASTNode, tgt: &ASTNode, out: &mut HashMap<String, ASTNode>) -> bool {
            match (tpl, tgt) {
                (ASTNode::Variable { name, .. }, v) => {
                    if let Some(k) = is_placeholder(name) {
                        out.insert(k.to_string(), v.clone());
                        true
                    } else {
                        tpl == tgt
                    }
                }
                (ASTNode::Literal { .. }, _)
                | (ASTNode::Me { .. }, _)
                | (ASTNode::This { .. }, _) => tpl == tgt,
                (
                    ASTNode::BinaryOp {
                        operator: op1,
                        left: l1,
                        right: r1,
                        ..
                    },
                    ASTNode::BinaryOp {
                        operator: op2,
                        left: l2,
                        right: r2,
                        ..
                    },
                ) => op1 == op2 && go(l1, l2, out) && go(r1, r2, out),
                (
                    ASTNode::UnaryOp {
                        operator: o1,
                        operand: a1,
                        ..
                    },
                    ASTNode::UnaryOp {
                        operator: o2,
                        operand: a2,
                        ..
                    },
                ) => o1 == o2 && go(a1, a2, out),
                (
                    ASTNode::MethodCall {
                        object: o1,
                        method: m1,
                        arguments: a1,
                        ..
                    },
                    ASTNode::MethodCall {
                        object: o2,
                        method: m2,
                        arguments: a2,
                        ..
                    },
                ) => {
                    if m1 != m2 {
                        return false;
                    }
                    if !go(o1, o2, out) {
                        return false;
                    }
                    // Support variadic anywhere in a1
                    let mut varpos: Option<(usize, String)> = None;
                    for (i, arg) in a1.iter().enumerate() {
                        if let ASTNode::Variable { name, .. } = arg {
                            if let Some(vn) = is_variadic(name) {
                                varpos = Some((i, vn.to_string()));
                                break;
                            }
                        }
                    }
                    if let Some((k, vn)) = varpos {
                        let suffix_len = a1.len() - k - 1;
                        if a2.len() < k + suffix_len {
                            return false;
                        }
                        for (x, y) in a1[..k].iter().zip(a2.iter()) {
                            if !go(x, y, out) {
                                return false;
                            }
                        }
                        for (i, x) in a1[a1.len() - suffix_len..].iter().enumerate() {
                            let y = &a2[a2.len() - suffix_len + i];
                            if !go(x, y, out) {
                                return false;
                            }
                        }
                        let tail: Vec<ASTNode> = a2[k..a2.len() - suffix_len].to_vec();
                        out.insert(
                            vn,
                            ASTNode::Program {
                                statements: tail,
                                span: nyash_rust::ast::Span::unknown(),
                            },
                        );
                        return true;
                    }
                    if a1.len() != a2.len() {
                        return false;
                    }
                    for (x, y) in a1.iter().zip(a2.iter()) {
                        if !go(x, y, out) {
                            return false;
                        }
                    }
                    true
                }
                (
                    ASTNode::FunctionCall {
                        name: n1,
                        arguments: a1,
                        ..
                    },
                    ASTNode::FunctionCall {
                        name: n2,
                        arguments: a2,
                        ..
                    },
                ) => {
                    if n1 != n2 {
                        return false;
                    }
                    let mut varpos: Option<(usize, String)> = None;
                    for (i, arg) in a1.iter().enumerate() {
                        if let ASTNode::Variable { name, .. } = arg {
                            if let Some(vn) = is_variadic(name) {
                                varpos = Some((i, vn.to_string()));
                                break;
                            }
                        }
                    }
                    if let Some((k, vn)) = varpos {
                        let suffix_len = a1.len() - k - 1;
                        if a2.len() < k + suffix_len {
                            return false;
                        }
                        for (x, y) in a1[..k].iter().zip(a2.iter()) {
                            if !go(x, y, out) {
                                return false;
                            }
                        }
                        for (i, x) in a1[a1.len() - suffix_len..].iter().enumerate() {
                            let y = &a2[a2.len() - suffix_len + i];
                            if !go(x, y, out) {
                                return false;
                            }
                        }
                        let tail: Vec<ASTNode> = a2[k..a2.len() - suffix_len].to_vec();
                        out.insert(
                            vn,
                            ASTNode::Program {
                                statements: tail,
                                span: nyash_rust::ast::Span::unknown(),
                            },
                        );
                        return true;
                    }
                    if a1.len() != a2.len() {
                        return false;
                    }
                    for (x, y) in a1.iter().zip(a2.iter()) {
                        if !go(x, y, out) {
                            return false;
                        }
                    }
                    true
                }
                (
                    ASTNode::ArrayLiteral { elements: e1, .. },
                    ASTNode::ArrayLiteral { elements: e2, .. },
                ) => {
                    let mut varpos: Option<(usize, String)> = None;
                    for (i, el) in e1.iter().enumerate() {
                        if let ASTNode::Variable { name, .. } = el {
                            if let Some(vn) = is_variadic(name) {
                                varpos = Some((i, vn.to_string()));
                                break;
                            }
                        }
                    }
                    if let Some((k, vn)) = varpos {
                        let suffix_len = e1.len() - k - 1;
                        if e2.len() < k + suffix_len {
                            return false;
                        }
                        for (x, y) in e1[..k].iter().zip(e2.iter()) {
                            if !go(x, y, out) {
                                return false;
                            }
                        }
                        for (i, x) in e1[e1.len() - suffix_len..].iter().enumerate() {
                            let y = &e2[e2.len() - suffix_len + i];
                            if !go(x, y, out) {
                                return false;
                            }
                        }
                        let tail: Vec<ASTNode> = e2[k..e2.len() - suffix_len].to_vec();
                        out.insert(
                            vn,
                            ASTNode::Program {
                                statements: tail,
                                span: nyash_rust::ast::Span::unknown(),
                            },
                        );
                        return true;
                    }
                    if e1.len() != e2.len() {
                        return false;
                    }
                    for (x, y) in e1.iter().zip(e2.iter()) {
                        if !go(x, y, out) {
                            return false;
                        }
                    }
                    true
                }
                (
                    ASTNode::MapLiteral { entries: m1, .. },
                    ASTNode::MapLiteral { entries: m2, .. },
                ) => {
                    if m1.len() != m2.len() {
                        return false;
                    }
                    for ((k1, v1), (k2, v2)) in m1.iter().zip(m2.iter()) {
                        if k1 != k2 {
                            return false;
                        }
                        if !go(v1, v2, out) {
                            return false;
                        }
                    }
                    true
                }
                (
                    ASTNode::FieldAccess {
                        object: o1,
                        field: f1,
                        ..
                    },
                    ASTNode::FieldAccess {
                        object: o2,
                        field: f2,
                        ..
                    },
                ) => f1 == f2 && go(o1, o2, out),
                (
                    ASTNode::Assignment {
                        target: t1,
                        value: v1,
                        ..
                    },
                    ASTNode::Assignment {
                        target: t2,
                        value: v2,
                        ..
                    },
                ) => go(t1, t2, out) && go(v1, v2, out),
                (ASTNode::Return { value: v1, .. }, ASTNode::Return { value: v2, .. }) => {
                    match (v1, v2) {
                        (Some(a), Some(b)) => go(a, b, out),
                        (None, None) => true,
                        _ => false,
                    }
                }
                (
                    ASTNode::If {
                        condition: c1,
                        then_body: t1,
                        else_body: e1,
                        ..
                    },
                    ASTNode::If {
                        condition: c2,
                        then_body: t2,
                        else_body: e2,
                        ..
                    },
                ) => {
                    if !go(c1, c2, out) || t1.len() != t2.len() {
                        return false;
                    }
                    for (x, y) in t1.iter().zip(t2.iter()) {
                        if !go(x, y, out) {
                            return false;
                        }
                    }
                    match (e1, e2) {
                        (Some(a), Some(b)) => {
                            if a.len() != b.len() {
                                return false;
                            }
                            for (x, y) in a.iter().zip(b.iter()) {
                                if !go(x, y, out) {
                                    return false;
                                }
                            }
                            true
                        }
                        (None, None) => true,
                        _ => false,
                    }
                }
                _ => tpl == tgt,
            }
        }
        let mut out = HashMap::new();
        if go(&self.template, node, &mut out) {
            Some(out)
        } else {
            None
        }
    }
}

/// ORパターン: いずれかのテンプレートにマッチすれば成功
pub struct OrPattern {
    pub alts: Vec<TemplatePattern>,
}

impl OrPattern {
    pub fn new(alts: Vec<TemplatePattern>) -> Self {
        Self { alts }
    }
}

impl MacroPattern for OrPattern {
    fn match_ast(&self, node: &ASTNode) -> Option<HashMap<String, ASTNode>> {
        for tp in &self.alts {
            if let Some(b) = tp.match_ast(node) {
                return Some(b);
            }
        }
        None
    }
}
