use std::ffi::CStr;

use inkwell::llvm_sys::core::{
    LLVMGetInstructionOpcode, LLVMGetNumOperandBundles, LLVMGetOperandBundleAtIndex,
    LLVMGetOperandBundleTag,
};
use inkwell::llvm_sys::prelude::{LLVMOperandBundleRef, LLVMValueRef};
use inkwell::llvm_sys::LLVMOpcode;

use super::Matcher;

#[derive(Clone)]
pub struct CallInstMatcher;

impl CallInstMatcher {
    pub fn create_call() -> Self {
        CallInstMatcher
    }
}

pub unsafe fn get_operand_bundle_name_sys<'a>(bundle_ref: LLVMOperandBundleRef) -> Option<&'a str> {
    let mut len = 0;
    let tag_ptr = LLVMGetOperandBundleTag(bundle_ref, &mut len);

    if tag_ptr.is_null() {
        None
    } else {
        // Create a CStr from the raw pointer and length.
        // `from_bytes_with_nul_unchecked` is okay here if we trust LLVM's guarantee
        // that the returned string is null-terminated and `len` is its length *before* the nul.
        let c_str =
            CStr::from_bytes_until_nul(std::slice::from_raw_parts(tag_ptr as *const u8, len + 1))
                .ok()?; // +1 to ensure it includes the potential null byte
        Some(c_str.to_str().unwrap_or("<invalid UTF-8>"))
    }
}

impl Matcher<LLVMValueRef> for CallInstMatcher {
    #[allow(clippy::not_unsafe_ptr_arg_deref)]
    fn is_match(&self, instruction: &LLVMValueRef) -> bool {
        unsafe {
            if LLVMGetNumOperandBundles(instruction.clone()).clone() > 0 {
                let bundle = LLVMGetOperandBundleAtIndex(*instruction, 0);
                println!("Amr {:?}", get_operand_bundle_name_sys(bundle));
            }

            LLVMGetInstructionOpcode(*instruction) == LLVMOpcode::LLVMCall
        }
    }
}
