use super::mir_json::common as mirjson_common;
use crate::mir::{
    function::{FunctionSignature, MirFunction, MirModule},
    BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId,
};
use serde_json::Value;

/// Parse minimal MIR JSON v0 (no schema_version, root has `functions` and each function has `blocks`).
/// Supported ops (minimal): const(i64), copy, compare(op/lhs/rhs), branch(cond/then/else), jump(target), phi(dst,incoming), ret(value?).
pub fn parse_mir_v0_to_module(json: &str) -> Result<MirModule, String> {
    let value: Value = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {}", e))?;
    let functions = value
        .get("functions")
        .and_then(|f| f.as_array())
        .ok_or_else(|| "JSON missing functions array".to_string())?;

    let mut module = MirModule::new("mir_json_v0".to_string());

    for func in functions {
        let func_name = func
            .get("name")
            .and_then(|n| n.as_str())
            .unwrap_or("main")
            .to_string();

        let blocks = func
            .get("blocks")
            .and_then(|b| b.as_array())
            .ok_or_else(|| format!("function '{}' missing blocks array", func_name))?;

        if blocks.is_empty() {
            return Err(format!("function '{}' has no blocks", func_name));
        }

        let entry_id = blocks
            .get(0)
            .and_then(|b| b.get("id"))
            .and_then(|id| id.as_u64())
            .ok_or_else(|| format!("function '{}' entry block missing id", func_name))?
            as u32;
        let entry_bb = BasicBlockId::new(entry_id);

        let mut signature = FunctionSignature {
            name: func_name.clone(),
            params: Vec::new(),
            return_type: MirType::Unknown,
            effects: crate::mir::EffectMask::PURE,
        };
        let mut mir_fn = MirFunction::new(signature.clone(), entry_bb);
        let mut max_value_id: u32 = 0;

        for block in blocks {
            let block_id = block
                .get("id")
                .and_then(|id| id.as_u64())
                .ok_or_else(|| format!("function '{}' block missing id", func_name))?
                as u32;
            let bb_id = BasicBlockId::new(block_id);
            if mir_fn.get_block(bb_id).is_none() {
                mir_fn.add_block(BasicBlock::new(bb_id));
            }
            let block_ref = mir_fn
                .get_block_mut(bb_id)
                .expect("block must exist after insertion");

            let instructions = block
                .get("instructions")
                .and_then(|insts| insts.as_array())
                .ok_or_else(|| {
                    format!(
                        "function '{}' block {} missing instructions array",
                        func_name, block_id
                    )
                })?;

            for inst in instructions {
                let op = inst.get("op").and_then(|o| o.as_str()).ok_or_else(|| {
                    format!(
                        "function '{}' block {} missing op field",
                        func_name, block_id
                    )
                })?;
                match op {
                    "const" => {
                        let dst = require_u64(inst, "dst", "const dst")? as u32;
                        let vobj = inst
                            .get("value")
                            .ok_or_else(|| "const missing value".to_string())?;
                        let val = parse_const_value(vobj)?;
                        block_ref.add_instruction(MirInstruction::Const {
                            dst: ValueId::new(dst),
                            value: val,
                        });
                        max_value_id = max_value_id.max(dst + 1);
                    }
                    "copy" => {
                        let dst = require_u64(inst, "dst", "copy dst")? as u32;
                        let src = require_u64(inst, "src", "copy src")? as u32;
                        block_ref.add_instruction(MirInstruction::Copy {
                            dst: ValueId::new(dst),
                            src: ValueId::new(src),
                        });
                        max_value_id = max_value_id.max(dst + 1);
                    }
                    "binop" => {
                        let dst = require_u64(inst, "dst", "binop dst")? as u32;
                        let lhs = require_u64(inst, "lhs", "binop lhs")? as u32;
                        let rhs = require_u64(inst, "rhs", "binop rhs")? as u32;
                        let operation = inst
                            .get("operation")
                            .and_then(Value::as_str)
                            .ok_or_else(|| "binop missing operation".to_string())?;
                        let bop = parse_binop(operation)?;
                        block_ref.add_instruction(MirInstruction::BinOp {
                            dst: ValueId::new(dst),
                            op: bop,
                            lhs: ValueId::new(lhs),
                            rhs: ValueId::new(rhs),
                        });
                        max_value_id = max_value_id.max(dst + 1);
                    }
                    "compare" => {
                        let dst = require_u64(inst, "dst", "compare dst")? as u32;
                        let lhs = require_u64(inst, "lhs", "compare lhs")? as u32;
                        let rhs = require_u64(inst, "rhs", "compare rhs")? as u32;
                        let operation = inst
                            .get("operation")
                            .and_then(Value::as_str)
                            .unwrap_or("==");
                        let cop = parse_compare(operation)?;
                        block_ref.add_instruction(MirInstruction::Compare {
                            dst: ValueId::new(dst),
                            op: cop,
                            lhs: ValueId::new(lhs),
                            rhs: ValueId::new(rhs),
                        });
                        max_value_id = max_value_id.max(dst + 1);
                    }
                    "branch" => {
                        let cond = require_u64(inst, "cond", "branch cond")? as u32;
                        let then_bb = require_u64(inst, "then", "branch then")? as u32;
                        let else_bb = require_u64(inst, "else", "branch else")? as u32;
                        block_ref.add_instruction(MirInstruction::Branch {
                            condition: ValueId::new(cond),
                            then_bb: BasicBlockId::new(then_bb),
                            else_bb: BasicBlockId::new(else_bb),
                            then_edge_args: None,
                            else_edge_args: None,
                        });
                    }
                    "jump" => {
                        let target = require_u64(inst, "target", "jump target")? as u32;
                        block_ref.add_instruction(MirInstruction::Jump {
                            target: BasicBlockId::new(target),
                            edge_args: None,
                        });
                    }
                    "phi" => {
                        let dst = require_u64(inst, "dst", "phi dst")? as u32;
                        let incoming = inst
                            .get("incoming")
                            .and_then(Value::as_array)
                            .ok_or_else(|| "phi incoming missing".to_string())?;
                        let mut pairs = Vec::with_capacity(incoming.len());
                        for entry in incoming {
                            let pair = entry
                                .as_array()
                                .ok_or_else(|| "phi incoming entry must be array".to_string())?;
                            if pair.len() != 2 {
                                return Err("phi incoming entry must have 2 elements".into());
                            }
                            let val = pair[0]
                                .as_u64()
                                .ok_or_else(|| "phi incoming value must be integer".to_string())?
                                as u32;
                            let bb = pair[1]
                                .as_u64()
                                .ok_or_else(|| "phi incoming block must be integer".to_string())?
                                as u32;
                            pairs.push((BasicBlockId::new(bb), ValueId::new(val)));
                        }
                        block_ref.add_instruction(MirInstruction::Phi {
                            dst: ValueId::new(dst),
                            inputs: pairs,
                            type_hint: None, // Phase 63-6
                        });
                        max_value_id = max_value_id.max(dst + 1);
                    }
                    "ret" => {
                        let value = inst
                            .get("value")
                            .and_then(|v| v.as_u64())
                            .map(|v| ValueId::new(v as u32));
                        block_ref.add_instruction(MirInstruction::Return { value });
                        if let Some(val) = value {
                            signature.return_type = MirType::Integer;
                            max_value_id = max_value_id.max(val.as_u32() + 1);
                        } else {
                            signature.return_type = MirType::Void;
                        }
                    }
                    "newbox" => {
                        let dst = require_u64(inst, "dst", "newbox dst")? as u32;
                        let ty = inst
                            .get("type")
                            .and_then(Value::as_str)
                            .ok_or_else(|| "newbox missing type".to_string())?
                            .to_string();
                        let args_v = inst
                            .get("args")
                            .and_then(Value::as_array)
                            .cloned()
                            .unwrap_or_default();
                        let mut args: Vec<ValueId> = Vec::with_capacity(args_v.len());
                        for a in args_v {
                            let id = a
                                .as_u64()
                                .ok_or_else(|| "newbox arg must be integer".to_string())?
                                as u32;
                            args.push(ValueId::new(id));
                        }
                        block_ref.add_instruction(MirInstruction::NewBox {
                            dst: ValueId::new(dst),
                            box_type: ty,
                            args,
                        });
                        max_value_id = max_value_id.max(dst + 1);
                    }
                    "boxcall" => {
                        // { op:"boxcall", box:<vid>, method:"name", args:[vid...], dst?:<vid> }
                        let box_id = require_u64(inst, "box", "boxcall box")? as u32;
                        let method = inst
                            .get("method")
                            .and_then(Value::as_str)
                            .ok_or_else(|| "boxcall missing method".to_string())?
                            .to_string();
                        let dst_opt = inst
                            .get("dst")
                            .and_then(Value::as_u64)
                            .map(|v| ValueId::new(v as u32));
                        let args_v = inst
                            .get("args")
                            .and_then(Value::as_array)
                            .cloned()
                            .unwrap_or_default();
                        let mut args: Vec<ValueId> = Vec::with_capacity(args_v.len());
                        for a in args_v {
                            let id = a
                                .as_u64()
                                .ok_or_else(|| "boxcall arg must be integer".to_string())?
                                as u32;
                            args.push(ValueId::new(id));
                        }
                        block_ref.add_instruction(MirInstruction::BoxCall {
                            dst: dst_opt,
                            box_val: ValueId::new(box_id),
                            method,
                            method_id: None,
                            args,
                            effects: EffectMask::READ,
                        });
                        if let Some(dv) = dst_opt {
                            max_value_id = max_value_id.max(dv.as_u32() + 1);
                        }
                    }
                    other => {
                        return Err(format!("unsupported op '{}' in mir_json_v0 loader", other));
                    }
                }
            }
        }

        // Set max value id (best effort)
        mir_fn.next_value_id = max_value_id;
        module.functions.insert(func_name.clone(), mir_fn);
    }

    Ok(module)
}

fn require_u64(node: &Value, key: &str, context: &str) -> Result<u64, String> {
    node.get(key)
        .and_then(Value::as_u64)
        .ok_or_else(|| format!("{} missing field '{}'", context, key))
}

fn parse_const_value(value_obj: &Value) -> Result<ConstValue, String> {
    // Delegate to common generic parser (int/float/bool/string/handle(StringBox))
    // Keeps behavior superset of previous (int-only) without changing existing callers.
    mirjson_common::parse_const_value_generic(value_obj).map_err(|e| format!("{}", e))
}

fn parse_compare(op: &str) -> Result<crate::mir::types::CompareOp, String> {
    use crate::mir::types::CompareOp;
    Ok(match op {
        "==" => CompareOp::Eq,
        "!=" => CompareOp::Ne,
        "<" => CompareOp::Lt,
        "<=" => CompareOp::Le,
        ">" => CompareOp::Gt,
        ">=" => CompareOp::Ge,
        s => return Err(format!("unsupported compare op '{}'", s)),
    })
}

fn parse_binop(op: &str) -> Result<crate::mir::types::BinaryOp, String> {
    use crate::mir::types::BinaryOp;
    Ok(match op {
        "+" => BinaryOp::Add,
        "-" => BinaryOp::Sub,
        "*" => BinaryOp::Mul,
        "/" => BinaryOp::Div,
        "%" => BinaryOp::Mod,
        s => return Err(format!("unsupported binary op '{}'", s)),
    })
}
