// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;

use super::types::{GuardMode, LogicalBlockMode, PolicyOp};
use super::{Instruction, InstructionData, LiteralOrRegister};

impl Instruction {
    /// Get detailed display string with parameter resolution for debugging
    pub fn display_with_params(&self, instruction_data: &InstructionData) -> String {
        match *self {
            Instruction::LoopStart { params_index } => {
                instruction_data.get_loop_params(params_index).map_or_else(
                    || format!("LOOP_START P({}) [INVALID INDEX]", params_index),
                    |params| {
                        format!(
                            "LOOP_START {:?} R({}) R({}) R({}) R({}) {} {}",
                            params.mode,
                            params.collection,
                            params.key_reg,
                            params.value_reg,
                            params.result_reg,
                            params.body_start,
                            params.loop_end
                        )
                    },
                )
            }
            Instruction::BuiltinCall { params_index } => instruction_data
                .get_builtin_call_params(params_index)
                .map_or_else(
                    || format!("BUILTIN_CALL P({}) [INVALID INDEX]", params_index),
                    |params| {
                        let args_str = params
                            .arg_registers()
                            .iter()
                            .map(|&r| format!("R({})", r))
                            .collect::<Vec<_>>()
                            .join(" ");
                        format!(
                            "BUILTIN_CALL R({}) B({}) [{}]",
                            params.dest, params.builtin_index, args_str
                        )
                    },
                ),
            Instruction::HostAwait { dest, arg, id } => {
                format!("HOST_AWAIT R({}) R({}) R({})", dest, arg, id)
            }
            Instruction::FunctionCall { params_index } => instruction_data
                .get_function_call_params(params_index)
                .map_or_else(
                    || format!("FUNCTION_CALL P({}) [INVALID INDEX]", params_index),
                    |params| {
                        let args_str = params
                            .arg_registers()
                            .iter()
                            .map(|&r| format!("R({})", r))
                            .collect::<Vec<_>>()
                            .join(" ");
                        format!(
                            "FUNCTION_CALL R({}) RULE({}) [{}]",
                            params.dest, params.func_rule_index, args_str
                        )
                    },
                ),
            Instruction::ObjectCreate { params_index } => instruction_data
                .get_object_create_params(params_index)
                .map_or_else(
                    || format!("OBJECT_CREATE P({}) [INVALID INDEX]", params_index),
                    |params| {
                        let mut field_parts = Vec::new();
                        for &(literal_idx, value_reg) in params.literal_key_field_pairs() {
                            field_parts.push(format!("L({}):R({})", literal_idx, value_reg));
                        }
                        for &(key_reg, value_reg) in params.field_pairs() {
                            field_parts.push(format!("R({}):R({})", key_reg, value_reg));
                        }
                        let fields_str = field_parts.join(" ");
                        format!(
                            "OBJECT_CREATE R({}) L({}) [{}]",
                            params.dest, params.template_literal_idx, fields_str
                        )
                    },
                ),
            Instruction::VirtualDataDocumentLookup { params_index } => instruction_data
                .get_virtual_data_document_lookup_params(params_index)
                .map_or_else(
                    || {
                        format!(
                            "VIRTUAL_DATA_DOCUMENT_LOOKUP P({}) [INVALID INDEX]",
                            params_index
                        )
                    },
                    |params| {
                        let components_str = params
                            .path_components
                            .iter()
                            .map(|comp| match *comp {
                                LiteralOrRegister::Literal(idx) => format!("L({})", idx),
                                LiteralOrRegister::Register(reg) => format!("R({})", reg),
                            })
                            .collect::<Vec<_>>()
                            .join(".");
                        format!(
                            "VIRTUAL_DATA_DOCUMENT_LOOKUP R({}) [data.{}]",
                            params.dest, components_str
                        )
                    },
                ),
            Instruction::ComprehensionBegin { params_index } => instruction_data
                .get_comprehension_begin_params(params_index)
                .map_or_else(
                    || format!("COMPREHENSION_BEGIN P({}) [INVALID INDEX]", params_index),
                    |params| {
                        format!(
                            "COMPREHENSION_BEGIN {:?} R({}) R({}) R({}) {} {}",
                            params.mode,
                            params.collection_reg,
                            params.key_reg,
                            params.value_reg,
                            params.body_start,
                            params.comprehension_end
                        )
                    },
                ),
            _ => format!("{}", self),
        }
    }
}

impl core::fmt::Display for Instruction {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let text = match *self {
            Instruction::Load { dest, literal_idx } => {
                format!("LOAD R({}) L({})", dest, literal_idx)
            }
            Instruction::LoadTrue { dest } => format!("LOAD_TRUE R({})", dest),
            Instruction::LoadFalse { dest } => format!("LOAD_FALSE R({})", dest),
            Instruction::LoadNull { dest } => format!("LOAD_NULL R({})", dest),
            Instruction::LoadBool { dest, value } => format!("LOAD_BOOL R({}) {}", dest, value),
            Instruction::LoadData { dest } => format!("LOAD_DATA R({})", dest),
            Instruction::LoadInput { dest } => format!("LOAD_INPUT R({})", dest),
            Instruction::LoadContext { dest } => format!("LOAD_CONTEXT R({})", dest),
            Instruction::LoadMetadata { dest } => format!("LOAD_METADATA R({})", dest),
            Instruction::Move { dest, src } => format!("MOVE R({}) R({})", dest, src),
            Instruction::Add { dest, left, right } => {
                format!("ADD R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Sub { dest, left, right } => {
                format!("SUB R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Mul { dest, left, right } => {
                format!("MUL R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Div { dest, left, right } => {
                format!("DIV R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Mod { dest, left, right } => {
                format!("MOD R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Eq { dest, left, right } => {
                format!("EQ R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Ne { dest, left, right } => {
                format!("NE R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Lt { dest, left, right } => {
                format!("LT R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Le { dest, left, right } => {
                format!("LE R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Gt { dest, left, right } => {
                format!("GT R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Ge { dest, left, right } => {
                format!("GE R({}) R({}) R({})", dest, left, right)
            }
            Instruction::And { dest, left, right } => {
                format!("AND R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Or { dest, left, right } => {
                format!("OR R({}) R({}) R({})", dest, left, right)
            }
            Instruction::Not { dest, operand } => {
                format!("NOT R({}) R({})", dest, operand)
            }
            Instruction::BuiltinCall { params_index } => {
                format!("BUILTIN_CALL P({})", params_index)
            }
            Instruction::HostAwait { dest, arg, id } => {
                format!("HOST_AWAIT R({}) R({}) R({})", dest, arg, id)
            }
            Instruction::FunctionCall { params_index } => {
                format!("FUNCTION_CALL P({})", params_index)
            }
            Instruction::Return { value } => format!("RETURN R({})", value),
            Instruction::ObjectSet { obj, key, value } => {
                format!("OBJECT_SET R({}) R({}) R({})", obj, key, value)
            }
            Instruction::ObjectCreate { params_index } => {
                format!("OBJECT_CREATE P({})", params_index)
            }
            Instruction::Index {
                dest,
                container,
                key,
            } => format!("INDEX R({}) R({}) R({})", dest, container, key),
            Instruction::IndexLiteral {
                dest,
                container,
                literal_idx,
            } => format!(
                "INDEX_LITERAL R({}) R({}) L({})",
                dest, container, literal_idx
            ),
            Instruction::ChainedIndex { params_index } => {
                format!("CHAINED_INDEX P({})", params_index)
            }
            Instruction::ArrayNew { dest } => format!("ARRAY_NEW R({})", dest),
            Instruction::ArrayPush { arr, value } => format!("ARRAY_PUSH R({}) R({})", arr, value),
            Instruction::ArrayPushDefined { arr, value } => {
                format!("ARRAY_PUSH_DEFINED R({}) R({})", arr, value)
            }
            Instruction::ArrayCreate { params_index } => {
                format!("ARRAY_CREATE P({})", params_index)
            }
            Instruction::SetNew { dest } => format!("SET_NEW R({})", dest),
            Instruction::SetAdd { set, value } => format!("SET_ADD R({}) R({})", set, value),
            Instruction::SetCreate { params_index } => {
                format!("SET_CREATE P({})", params_index)
            }
            Instruction::Contains {
                dest,
                collection,
                value,
            } => format!("CONTAINS R({}) R({}) R({})", dest, collection, value),
            Instruction::Count { dest, collection } => {
                format!("COUNT R({}) R({})", dest, collection)
            }
            Instruction::AssertEq { left, right } => {
                format!("ASSERT_EQ R({}) R({})", left, right)
            }
            Instruction::Guard { register, mode } => {
                let name = match mode {
                    GuardMode::Not => "ASSERT_NOT",
                    GuardMode::Condition => "ASSERT_CONDITION",
                    GuardMode::NotUndefined => "ASSERT_NOT_UNDEFINED",
                };
                format!("{} R({})", name, register)
            }
            Instruction::ReturnUndefinedIfNotTrue { condition } => {
                format!("RETURN_UNDEFINED_IF_NOT_TRUE R({})", condition)
            }
            Instruction::CoalesceUndefinedToNull { register } => {
                format!("COALESCE_UNDEF_TO_NULL R({})", register)
            }
            Instruction::LoopStart { params_index } => {
                format!("LOOP_START P({})", params_index)
            }
            Instruction::LoopNext {
                body_start,
                loop_end,
            } => {
                format!("LOOP_NEXT {} {}", body_start, loop_end)
            }
            Instruction::CallRule { dest, rule_index } => {
                format!("CALL_RULE R({}) {}", dest, rule_index)
            }
            Instruction::VirtualDataDocumentLookup { params_index } => {
                format!("VIRTUAL_DATA_DOCUMENT_LOOKUP P({})", params_index)
            }
            Instruction::DestructuringSuccess {} => String::from("DESTRUCTURING_SUCCESS"),
            Instruction::RuleReturn {} => String::from("RULE_RETURN"),

            Instruction::RuleInit {
                result_reg,
                rule_index,
            } => {
                format!("RULE_INIT R({}) {}", result_reg, rule_index)
            }
            Instruction::Halt {} => String::from("HALT"),
            Instruction::ComprehensionBegin { params_index } => {
                format!("COMPREHENSION_BEGIN P({})", params_index)
            }
            Instruction::ComprehensionYield { value_reg, key_reg } => key_reg.as_ref().map_or_else(
                || format!("COMPREHENSION_YIELD R({})", value_reg),
                |k| format!("COMPREHENSION_YIELD R({}) R({})", k, value_reg),
            ),
            Instruction::ComprehensionEnd {} => String::from("COMPREHENSION_END"),

            // Azure Policy consolidated instruction
            Instruction::PolicyCondition {
                dest,
                left,
                right,
                op,
            } => match op {
                PolicyOp::Not => format!("{} R({}) R({})", op.display_name(), dest, left),
                _ => format!("{} R({}) R({}) R({})", op.display_name(), dest, left, right),
            },

            // AllOf / AnyOf structured instructions
            Instruction::LogicalBlockStart {
                mode,
                result,
                end_pc,
            } => {
                let name = match mode {
                    LogicalBlockMode::AllOf => "ALL_OF_START",
                    LogicalBlockMode::AnyOf => "ANY_OF_START",
                };
                format!("{} R({}) {}", name, result, end_pc)
            }
            Instruction::AllOfNext {
                check,
                result,
                end_pc,
            } => {
                format!("ALL_OF_NEXT R({}) R({}) {}", check, result, end_pc)
            }
            Instruction::AnyOfNext {
                check,
                result,
                end_pc,
            } => {
                format!("ANY_OF_NEXT R({}) R({}) {}", check, result, end_pc)
            }
            Instruction::LogicalBlockEnd { mode, result } => {
                let name = match mode {
                    LogicalBlockMode::AllOf => "ALL_OF_END",
                    LogicalBlockMode::AnyOf => "ANY_OF_END",
                };
                format!("{} R({})", name, result)
            }
        };
        write!(f, "{}", text)
    }
}
