use nyash_rust::ast::{ASTNode, BinaryOperator, LiteralValue, Span, UnaryOperator};
use serde_json::{json, Value};

pub fn ast_to_json(ast: &ASTNode) -> Value {
    match ast.clone() {
        ASTNode::Program { statements, .. } => json!({
            "kind": "Program",
            "statements": statements.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
        }),
        // Phase 54: Loop with JoinIR-compatible fields
        ASTNode::Loop {
            condition, body, ..
        } => json!({
            "kind": "Loop",
            "type": "Loop",  // JoinIR Frontend expects "type"
            "condition": ast_to_json(&condition),
            "cond": ast_to_json(&condition),  // JoinIR expects "cond"
            "body": body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
        }),
        // Phase 54: Print with JoinIR-compatible fields
        ASTNode::Print { expression, .. } => json!({
            "kind": "Print",
            "type": "Print",  // JoinIR Frontend expects "type"
            "expression": ast_to_json(&expression),
            "expr": ast_to_json(&expression),  // JoinIR expects "expr"
        }),
        // Phase 54: Return with JoinIR-compatible fields
        ASTNode::Return { value, .. } => json!({
            "kind": "Return",
            "type": "Return",  // JoinIR Frontend expects "type"
            "value": value.as_ref().map(|v| ast_to_json(v)),
        }),
        // Phase 56: Break with JoinIR-compatible type field
        ASTNode::Break { .. } => json!({
            "kind": "Break",
            "type": "Break"  // JoinIR Frontend expects "type"
        }),
        // Phase 56: Continue with JoinIR-compatible type field
        ASTNode::Continue { .. } => json!({
            "kind": "Continue",
            "type": "Continue"  // JoinIR Frontend expects "type"
        }),
        // Phase 54: Assignment with JoinIR-compatible fields
        ASTNode::Assignment { target, value, .. } => {
            // Extract variable name if target is a simple Variable
            let target_str = match target.as_ref() {
                ASTNode::Variable { name, .. } => name.clone(),
                _ => "complex".to_string(), // FieldAccess or other complex target
            };
            json!({
                "kind": "Assignment",
                "type": "Assignment",  // JoinIR Frontend expects "type"
                "target": target_str,  // JoinIR expects string variable name
                "lhs": ast_to_json(&target),  // Keep full AST for complex cases
                "value": ast_to_json(&value),
                "expr": ast_to_json(&value),  // JoinIR expects "expr"
            })
        }
        // Phase 54: Local with JoinIR-compatible fields
        ASTNode::Local {
            variables,
            initial_values,
            ..
        } => {
            // For single-variable declarations, add "name" and "expr" for JoinIR compatibility
            let (name, expr) = if variables.len() == 1 {
                let n = variables[0].clone();
                let e = initial_values
                    .get(0)
                    .and_then(|opt| opt.as_ref())
                    .map(|v| ast_to_json(v));
                (Some(n), e)
            } else {
                (None, None)
            };

            let inits: Vec<_> = initial_values
                .into_iter()
                .map(|opt| opt.map(|v| ast_to_json(&v)))
                .collect();

            json!({
                "kind": "Local",
                "type": "Local",  // JoinIR Frontend expects "type"
                "name": name,  // Single variable name for JoinIR (null if multiple)
                "expr": expr,  // Single variable init for JoinIR (null if multiple)
                "variables": variables,
                "inits": inits
            })
        }
        // Phase 54: If with JoinIR-compatible fields
        ASTNode::If {
            condition,
            then_body,
            else_body,
            ..
        } => json!({
            "kind": "If",
            "type": "If",  // JoinIR Frontend expects "type"
            "condition": ast_to_json(&condition),
            "cond": ast_to_json(&condition),  // JoinIR expects "cond"
            "then": then_body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
            "else": else_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()),
        }),
        ASTNode::TryCatch {
            try_body,
            catch_clauses,
            finally_body,
            ..
        } => json!({
            "kind": "TryCatch",
            "try": try_body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
            "catch": catch_clauses.into_iter().map(|cc| json!({
                "type": cc.exception_type,
                "var": cc.variable_name,
                "body": cc.body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
            })).collect::<Vec<_>>(),
            "cleanup": finally_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>())
        }),
        ASTNode::FunctionDeclaration {
            name,
            params,
            body,
            is_static,
            is_override,
            ..
        } => json!({
            "kind": "FunctionDeclaration",
            "name": name,
            "params": params,
            "body": body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
            "static": is_static,
            "override": is_override,
        }),
        // Phase 52: Variable → Var ノード（JoinIR Frontend 互換）
        ASTNode::Variable { name, .. } => json!({
            "kind": "Variable",
            "type": "Var",  // JoinIR Frontend expects "type": "Var"
            "name": name
        }),
        // Phase 52: Literal → Int/Bool/String ノード（JoinIR Frontend 互換）
        ASTNode::Literal { value, .. } => literal_to_joinir_json(&value),
        // Phase 52: BinaryOp → Binary/Compare ノード（JoinIR Frontend 互換）
        ASTNode::BinaryOp {
            operator,
            left,
            right,
            ..
        } => {
            let op_str = bin_to_str(&operator);
            // JoinIR Frontend distinguishes between Binary (arithmetic) and Compare
            let type_str = if is_compare_op(&operator) {
                "Compare"
            } else {
                "Binary"
            };
            json!({
                "kind": "BinaryOp",
                "type": type_str,
                "op": op_str,
                // JoinIR Frontend expects "lhs"/"rhs" not "left"/"right"
                "lhs": ast_to_json(&left),
                "rhs": ast_to_json(&right),
                // Also keep "left"/"right" for backward compatibility
                "left": ast_to_json(&left),
                "right": ast_to_json(&right),
            })
        }
        // Phase 56: UnaryOp → Unary ノード（JoinIR Frontend 互換）
        ASTNode::UnaryOp {
            operator, operand, ..
        } => json!({
            "kind": "UnaryOp",
            "type": "Unary",  // Phase 56: JoinIR Frontend expects "type" field
            "op": un_to_str(&operator),
            "operand": ast_to_json(&operand),
        }),
        // Phase 52: MethodCall → Method ノード（JoinIR Frontend 互換）
        ASTNode::MethodCall {
            object,
            method,
            arguments,
            ..
        } => json!({
            "kind": "MethodCall",
            "type": "Method",  // JoinIR Frontend expects "type": "Method"
            // JoinIR Frontend expects "receiver" not "object"
            "receiver": ast_to_json(&object),
            "object": ast_to_json(&object),  // Keep for backward compatibility
            "method": method,
            // JoinIR Frontend expects "args" not "arguments"
            "args": arguments.iter().map(|a| ast_to_json(a)).collect::<Vec<_>>(),
            "arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()  // Keep for backward compatibility
        }),
        // Phase 56: FunctionCall with JoinIR-compatible type field
        ASTNode::FunctionCall {
            name, arguments, ..
        } => json!({
            "kind": "FunctionCall",
            "type": "Call",  // JoinIR Frontend expects "type": "Call"
            "name": name,
            "func": name.clone(),  // JoinIR expects "func" for function name
            "args": arguments.iter().map(|a| ast_to_json(a)).collect::<Vec<_>>(),  // JoinIR expects "args"
            "arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()  // Keep for backward compatibility
        }),
        // Phase 56: ArrayLiteral with JoinIR-compatible type field
        ASTNode::ArrayLiteral { elements, .. } => json!({
            "kind": "Array",
            "type": "Array",  // JoinIR Frontend expects "type"
            "elements": elements.into_iter().map(|e| ast_to_json(&e)).collect::<Vec<_>>()
        }),
        // Phase 56: MapLiteral with JoinIR-compatible type field
        ASTNode::MapLiteral { entries, .. } => json!({
            "kind": "Map",
            "type": "Map",  // JoinIR Frontend expects "type"
            "entries": entries.into_iter().map(|(k,v)| json!({"k":k,"v":ast_to_json(&v)})).collect::<Vec<_>>()
        }),
        ASTNode::MatchExpr {
            scrutinee,
            arms,
            else_expr,
            ..
        } => json!({
            "kind":"MatchExpr",
            "scrutinee": ast_to_json(&scrutinee),
            "arms": arms.into_iter().map(|(lit, body)| json!({
                "literal": {
                    "kind": "Literal",
                    "value": lit_to_json(&lit)
                },
                "body": ast_to_json(&body)
            })).collect::<Vec<_>>(),
            "else": ast_to_json(&else_expr),
        }),
        // Phase 52: FieldAccess → Field ノード（JoinIR Frontend 互換）
        ASTNode::FieldAccess { object, field, .. } => json!({
            "kind": "FieldAccess",
            "type": "Field",  // JoinIR Frontend expects "type": "Field"
            "object": ast_to_json(&object),
            "field": field
        }),
        // Phase 52: Me → Var("me") ノード（JoinIR Frontend 互換）
        ASTNode::Me { .. } => json!({
            "kind": "Me",
            "type": "Var",  // JoinIR Frontend expects "type": "Var"
            "name": "me"
        }),
        // Phase 52: New → NewBox ノード（JoinIR Frontend 互換）
        ASTNode::New {
            class, arguments, ..
        } => json!({
            "kind": "New",
            "type": "NewBox",  // JoinIR Frontend expects "type": "NewBox"
            "box_name": class,
            "args": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()
        }),
        other => json!({"kind":"Unsupported","debug": format!("{:?}", other)}),
    }
}

pub fn json_to_ast(v: &Value) -> Option<ASTNode> {
    let k = v.get("kind")?.as_str()?;
    Some(match k {
        "Program" => {
            let stmts = v
                .get("statements")?
                .as_array()?
                .iter()
                .filter_map(json_to_ast)
                .collect::<Vec<_>>();
            ASTNode::Program {
                statements: stmts,
                span: Span::unknown(),
            }
        }
        "Loop" => ASTNode::Loop {
            condition: Box::new(json_to_ast(v.get("condition")?)?),
            body: v
                .get("body")?
                .as_array()?
                .iter()
                .filter_map(json_to_ast)
                .collect::<Vec<_>>(),
            span: Span::unknown(),
        },
        "Print" => ASTNode::Print {
            expression: Box::new(json_to_ast(v.get("expression")?)?),
            span: Span::unknown(),
        },
        "Return" => ASTNode::Return {
            value: v.get("value").and_then(json_to_ast).map(Box::new),
            span: Span::unknown(),
        },
        "Break" => ASTNode::Break {
            span: Span::unknown(),
        },
        "Continue" => ASTNode::Continue {
            span: Span::unknown(),
        },
        "Assignment" => ASTNode::Assignment {
            target: Box::new(json_to_ast(v.get("target")?)?),
            value: Box::new(json_to_ast(v.get("value")?)?),
            span: Span::unknown(),
        },
        "Local" => {
            let vars = v
                .get("variables")?
                .as_array()?
                .iter()
                .filter_map(|s| s.as_str().map(|x| x.to_string()))
                .collect();
            let inits = v
                .get("inits")?
                .as_array()?
                .iter()
                .map(|initv| {
                    if initv.is_null() {
                        None
                    } else {
                        json_to_ast(initv).map(Box::new)
                    }
                })
                .collect();
            ASTNode::Local {
                variables: vars,
                initial_values: inits,
                span: Span::unknown(),
            }
        }
        "If" => ASTNode::If {
            condition: Box::new(json_to_ast(v.get("condition")?)?),
            then_body: v
                .get("then")?
                .as_array()?
                .iter()
                .filter_map(json_to_ast)
                .collect::<Vec<_>>(),
            else_body: v.get("else").and_then(|a| {
                a.as_array()
                    .map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())
            }),
            span: Span::unknown(),
        },
        "FunctionDeclaration" => ASTNode::FunctionDeclaration {
            name: v.get("name")?.as_str()?.to_string(),
            params: v
                .get("params")?
                .as_array()?
                .iter()
                .filter_map(|s| s.as_str().map(|x| x.to_string()))
                .collect(),
            body: v
                .get("body")?
                .as_array()?
                .iter()
                .filter_map(json_to_ast)
                .collect(),
            is_static: v.get("static").and_then(|b| b.as_bool()).unwrap_or(false),
            is_override: v.get("override").and_then(|b| b.as_bool()).unwrap_or(false),
            span: Span::unknown(),
        },
        "Variable" => ASTNode::Variable {
            name: v.get("name")?.as_str()?.to_string(),
            span: Span::unknown(),
        },
        "Literal" => ASTNode::Literal {
            value: json_to_lit(v.get("value")?)?,
            span: Span::unknown(),
        },
        "BinaryOp" => ASTNode::BinaryOp {
            operator: str_to_bin(v.get("op")?.as_str()?)?,
            left: Box::new(json_to_ast(v.get("left")?)?),
            right: Box::new(json_to_ast(v.get("right")?)?),
            span: Span::unknown(),
        },
        "UnaryOp" => ASTNode::UnaryOp {
            operator: str_to_un(v.get("op")?.as_str()?)?,
            operand: Box::new(json_to_ast(v.get("operand")?)?),
            span: Span::unknown(),
        },
        "MethodCall" => ASTNode::MethodCall {
            object: Box::new(json_to_ast(v.get("object")?)?),
            method: v.get("method")?.as_str()?.to_string(),
            arguments: v
                .get("arguments")?
                .as_array()?
                .iter()
                .filter_map(json_to_ast)
                .collect(),
            span: Span::unknown(),
        },
        "FunctionCall" => ASTNode::FunctionCall {
            name: v.get("name")?.as_str()?.to_string(),
            arguments: v
                .get("arguments")?
                .as_array()?
                .iter()
                .filter_map(json_to_ast)
                .collect(),
            span: Span::unknown(),
        },
        "Array" => ASTNode::ArrayLiteral {
            elements: v
                .get("elements")?
                .as_array()?
                .iter()
                .filter_map(json_to_ast)
                .collect(),
            span: Span::unknown(),
        },
        "Map" => ASTNode::MapLiteral {
            entries: v
                .get("entries")?
                .as_array()?
                .iter()
                .filter_map(|e| {
                    Some((e.get("k")?.as_str()?.to_string(), json_to_ast(e.get("v")?)?))
                })
                .collect(),
            span: Span::unknown(),
        },
        "MatchExpr" => {
            let scr = json_to_ast(v.get("scrutinee")?)?;
            let arms_json = v.get("arms")?.as_array()?.iter();
            let mut arms = Vec::new();
            for arm_v in arms_json {
                let lit_val = arm_v.get("literal")?.get("value")?;
                let lit = json_to_lit(lit_val)?;
                let body = json_to_ast(arm_v.get("body")?)?;
                arms.push((lit, body));
            }
            let else_expr = json_to_ast(v.get("else")?)?;
            ASTNode::MatchExpr {
                scrutinee: Box::new(scr),
                arms,
                else_expr: Box::new(else_expr),
                span: Span::unknown(),
            }
        }
        "TryCatch" => {
            let try_b = v
                .get("try")?
                .as_array()?
                .iter()
                .filter_map(json_to_ast)
                .collect::<Vec<_>>();
            let mut catches = Vec::new();
            if let Some(arr) = v.get("catch").and_then(|x| x.as_array()) {
                for c in arr.iter() {
                    let exc_t = match c.get("type") {
                        Some(t) if !t.is_null() => t.as_str().map(|s| s.to_string()),
                        _ => None,
                    };
                    let var = match c.get("var") {
                        Some(vv) if !vv.is_null() => vv.as_str().map(|s| s.to_string()),
                        _ => None,
                    };
                    let body = c
                        .get("body")?
                        .as_array()?
                        .iter()
                        .filter_map(json_to_ast)
                        .collect::<Vec<_>>();
                    catches.push(nyash_rust::ast::CatchClause {
                        exception_type: exc_t,
                        variable_name: var,
                        body,
                        span: Span::unknown(),
                    });
                }
            }
            let cleanup = v.get("cleanup").and_then(|cl| {
                cl.as_array()
                    .map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())
            });
            ASTNode::TryCatch {
                try_body: try_b,
                catch_clauses: catches,
                finally_body: cleanup,
                span: Span::unknown(),
            }
        }
        _ => return None,
    })
}

fn lit_to_json(v: &LiteralValue) -> Value {
    match v {
        LiteralValue::String(s) => json!({"type":"string","value":s}),
        LiteralValue::Integer(i) => json!({"type":"int","value":i}),
        LiteralValue::Float(f) => json!({"type":"float","value":f}),
        LiteralValue::Bool(b) => json!({"type":"bool","value":b}),
        LiteralValue::Null => json!({"type":"null"}),
        LiteralValue::Void => json!({"type":"void"}),
    }
}

fn json_to_lit(v: &Value) -> Option<LiteralValue> {
    let t = v.get("type")?.as_str()?;
    Some(match t {
        "string" => LiteralValue::String(v.get("value")?.as_str()?.to_string()),
        "int" => LiteralValue::Integer(v.get("value")?.as_i64()?),
        "float" => LiteralValue::Float(v.get("value")?.as_f64()?),
        "bool" => LiteralValue::Bool(v.get("value")?.as_bool()?),
        "null" => LiteralValue::Null,
        "void" => LiteralValue::Void,
        _ => return None,
    })
}

fn bin_to_str(op: &BinaryOperator) -> &'static str {
    match op {
        BinaryOperator::Add => "+",
        BinaryOperator::Subtract => "-",
        BinaryOperator::Multiply => "*",
        BinaryOperator::Divide => "/",
        BinaryOperator::Modulo => "%",
        BinaryOperator::BitAnd => "&",
        BinaryOperator::BitOr => "|",
        BinaryOperator::BitXor => "^",
        BinaryOperator::Shl => "<<",
        BinaryOperator::Shr => ">>",
        BinaryOperator::Equal => "==",
        BinaryOperator::NotEqual => "!=",
        BinaryOperator::Less => "<",
        BinaryOperator::Greater => ">",
        BinaryOperator::LessEqual => "<=",
        BinaryOperator::GreaterEqual => ">=",
        BinaryOperator::And => "&&",
        BinaryOperator::Or => "||",
    }
}

fn str_to_bin(s: &str) -> Option<BinaryOperator> {
    Some(match s {
        "+" => BinaryOperator::Add,
        "-" => BinaryOperator::Subtract,
        "*" => BinaryOperator::Multiply,
        "/" => BinaryOperator::Divide,
        "%" => BinaryOperator::Modulo,
        "&" => BinaryOperator::BitAnd,
        "|" => BinaryOperator::BitOr,
        "^" => BinaryOperator::BitXor,
        "<<" => BinaryOperator::Shl,
        ">>" => BinaryOperator::Shr,
        "==" => BinaryOperator::Equal,
        "!=" => BinaryOperator::NotEqual,
        "<" => BinaryOperator::Less,
        ">" => BinaryOperator::Greater,
        "<=" => BinaryOperator::LessEqual,
        ">=" => BinaryOperator::GreaterEqual,
        "&&" => BinaryOperator::And,
        "||" => BinaryOperator::Or,
        _ => return None,
    })
}

fn un_to_str(op: &UnaryOperator) -> &'static str {
    match op {
        UnaryOperator::Minus => "-",
        UnaryOperator::Not => "not",
        UnaryOperator::BitNot => "~",
        UnaryOperator::Weak => "weak", // Phase 285W-Syntax-0
    }
}

fn str_to_un(s: &str) -> Option<UnaryOperator> {
    Some(match s {
        "-" => UnaryOperator::Minus,
        "not" => UnaryOperator::Not,
        "~" => UnaryOperator::BitNot,
        "weak" => UnaryOperator::Weak, // Phase 285W-Syntax-0
        _ => return None,
    })
}

/// Phase 52: Check if a binary operator is a comparison operator
fn is_compare_op(op: &BinaryOperator) -> bool {
    matches!(
        op,
        BinaryOperator::Equal
            | BinaryOperator::NotEqual
            | BinaryOperator::Less
            | BinaryOperator::Greater
            | BinaryOperator::LessEqual
            | BinaryOperator::GreaterEqual
    )
}

/// Phase 52: Convert a literal value to JoinIR-compatible JSON format
///
/// JoinIR Frontend expects:
/// - Integer: {"type": "Int", "value": <number>}
/// - Boolean: {"type": "Bool", "value": <bool>}
/// - String: {"type": "String", "value": <string>}
fn literal_to_joinir_json(v: &LiteralValue) -> Value {
    match v {
        LiteralValue::Integer(i) => json!({
            "kind": "Literal",
            "type": "Int",  // JoinIR Frontend expects "type": "Int"
            "value": i
        }),
        LiteralValue::Bool(b) => json!({
            "kind": "Literal",
            "type": "Bool",  // JoinIR Frontend expects "type": "Bool"
            "value": b
        }),
        LiteralValue::String(s) => json!({
            "kind": "Literal",
            "type": "String",
            "value": s
        }),
        LiteralValue::Float(f) => json!({
            "kind": "Literal",
            "type": "Float",
            "value": f
        }),
        LiteralValue::Null => json!({
            "kind": "Literal",
            "type": "Null"
        }),
        LiteralValue::Void => json!({
            "kind": "Literal",
            "type": "Void"
        }),
    }
}
