//! Raw host function bindings.
//!
//! These are the low-level extern functions imported from the Synor VM.
//! Most contracts should use the higher-level wrappers in other modules.

// Import host functions from the "env" module
#[cfg(target_arch = "wasm32")]
extern "C" {
    // Storage operations
    fn synor_storage_read(key_ptr: i32, out_ptr: i32) -> i32;
    fn synor_storage_write(key_ptr: i32, value_ptr: i32, value_len: i32) -> i32;
    fn synor_storage_delete(key_ptr: i32) -> i32;

    // Context getters
    fn synor_get_caller(out_ptr: i32);
    fn synor_get_address(out_ptr: i32);
    fn synor_get_origin(out_ptr: i32);
    fn synor_get_value() -> i64;
    fn synor_get_calldata_size() -> i32;
    fn synor_get_calldata(out_ptr: i32, offset: i32, length: i32);
    fn synor_get_timestamp() -> i64;
    fn synor_get_block_height() -> i64;
    fn synor_get_block_hash(out_ptr: i32);
    fn synor_get_gas_remaining() -> i64;
    fn synor_get_chain_id() -> i64;

    // Cryptographic operations
    fn synor_sha3(data_ptr: i32, data_len: i32, out_ptr: i32);
    fn synor_blake3(data_ptr: i32, data_len: i32, out_ptr: i32);
    fn synor_verify_ed25519(msg_ptr: i32, msg_len: i32, sig_ptr: i32, pk_ptr: i32) -> i32;

    // Events
    fn synor_emit_log(topics_ptr: i32, topics_count: i32, data_ptr: i32, data_len: i32) -> i32;

    // Cross-contract calls
    fn synor_call(addr_ptr: i32, value: i64, data_ptr: i32, data_len: i32, gas: i64) -> i32;
    fn synor_static_call(addr_ptr: i32, data_ptr: i32, data_len: i32, gas: i64) -> i32;
    fn synor_delegate_call(addr_ptr: i32, data_ptr: i32, data_len: i32, gas: i64) -> i32;

    // Return data from calls
    fn synor_return_data_size() -> i32;
    fn synor_return_data_copy(dest_ptr: i32, offset: i32, length: i32);

    // Control flow
    fn synor_revert(msg_ptr: i32, msg_len: i32);
    fn synor_return(data_ptr: i32, data_len: i32);

    // Balance
    fn synor_get_balance(addr_ptr: i32) -> i64;
    fn synor_transfer(addr_ptr: i32, amount: i64) -> i32;
}

// ==================== Storage ====================

/// Reads from storage. Returns the length of data read, or -1 if not found.
#[cfg(target_arch = "wasm32")]
pub fn storage_read(key: &[u8; 32], out: &mut [u8]) -> i32 {
    unsafe { synor_storage_read(key.as_ptr() as i32, out.as_mut_ptr() as i32) }
}

/// Writes to storage.
#[cfg(target_arch = "wasm32")]
pub fn storage_write(key: &[u8; 32], value: &[u8]) -> i32 {
    unsafe {
        synor_storage_write(
            key.as_ptr() as i32,
            value.as_ptr() as i32,
            value.len() as i32,
        )
    }
}

/// Deletes from storage. Returns 1 if key existed, 0 otherwise.
#[cfg(target_arch = "wasm32")]
pub fn storage_delete(key: &[u8; 32]) -> i32 {
    unsafe { synor_storage_delete(key.as_ptr() as i32) }
}

// ==================== Context ====================

/// Gets the caller address.
#[cfg(target_arch = "wasm32")]
pub fn get_caller() -> [u8; 34] {
    let mut out = [0u8; 34];
    unsafe { synor_get_caller(out.as_mut_ptr() as i32) };
    out
}

/// Gets the contract's own address.
#[cfg(target_arch = "wasm32")]
pub fn get_address() -> [u8; 34] {
    let mut out = [0u8; 34];
    unsafe { synor_get_address(out.as_mut_ptr() as i32) };
    out
}

/// Gets the original transaction sender.
#[cfg(target_arch = "wasm32")]
pub fn get_origin() -> [u8; 34] {
    let mut out = [0u8; 34];
    unsafe { synor_get_origin(out.as_mut_ptr() as i32) };
    out
}

/// Gets the value sent with the call.
#[cfg(target_arch = "wasm32")]
pub fn get_value() -> u64 {
    unsafe { synor_get_value() as u64 }
}

/// Gets the calldata size.
#[cfg(target_arch = "wasm32")]
pub fn get_calldata_size() -> usize {
    unsafe { synor_get_calldata_size() as usize }
}

/// Gets calldata into the provided buffer.
#[cfg(target_arch = "wasm32")]
pub fn get_calldata(out: &mut [u8], offset: usize) {
    unsafe { synor_get_calldata(out.as_mut_ptr() as i32, offset as i32, out.len() as i32) };
}

/// Gets the block timestamp.
#[cfg(target_arch = "wasm32")]
pub fn get_timestamp() -> u64 {
    unsafe { synor_get_timestamp() as u64 }
}

/// Gets the block height.
#[cfg(target_arch = "wasm32")]
pub fn get_block_height() -> u64 {
    unsafe { synor_get_block_height() as u64 }
}

/// Gets the block hash.
#[cfg(target_arch = "wasm32")]
pub fn get_block_hash() -> [u8; 32] {
    let mut out = [0u8; 32];
    unsafe { synor_get_block_hash(out.as_mut_ptr() as i32) };
    out
}

/// Gets remaining gas.
#[cfg(target_arch = "wasm32")]
pub fn get_gas_remaining() -> u64 {
    unsafe { synor_get_gas_remaining() as u64 }
}

/// Gets the chain ID.
#[cfg(target_arch = "wasm32")]
pub fn get_chain_id() -> u64 {
    unsafe { synor_get_chain_id() as u64 }
}

// ==================== Crypto ====================

/// Computes SHA3-256 hash.
#[cfg(target_arch = "wasm32")]
pub fn sha3(data: &[u8]) -> [u8; 32] {
    let mut out = [0u8; 32];
    unsafe {
        synor_sha3(
            data.as_ptr() as i32,
            data.len() as i32,
            out.as_mut_ptr() as i32,
        )
    };
    out
}

/// Computes Blake3 hash.
#[cfg(target_arch = "wasm32")]
pub fn blake3(data: &[u8]) -> [u8; 32] {
    let mut out = [0u8; 32];
    unsafe {
        synor_blake3(
            data.as_ptr() as i32,
            data.len() as i32,
            out.as_mut_ptr() as i32,
        )
    };
    out
}

/// Verifies an Ed25519 signature.
#[cfg(target_arch = "wasm32")]
pub fn verify_ed25519(message: &[u8], signature: &[u8; 64], public_key: &[u8; 32]) -> bool {
    unsafe {
        synor_verify_ed25519(
            message.as_ptr() as i32,
            message.len() as i32,
            signature.as_ptr() as i32,
            public_key.as_ptr() as i32,
        ) == 1
    }
}

// ==================== Events ====================

/// Emits a log event.
#[cfg(target_arch = "wasm32")]
pub fn emit_log(topics: &[[u8; 32]], data: &[u8]) -> i32 {
    unsafe {
        synor_emit_log(
            topics.as_ptr() as i32,
            topics.len() as i32,
            data.as_ptr() as i32,
            data.len() as i32,
        )
    }
}

// ==================== Calls ====================

/// Calls another contract.
#[cfg(target_arch = "wasm32")]
pub fn call(address: &[u8; 32], value: u64, data: &[u8], gas: u64) -> i32 {
    unsafe {
        synor_call(
            address.as_ptr() as i32,
            value as i64,
            data.as_ptr() as i32,
            data.len() as i32,
            gas as i64,
        )
    }
}

/// Makes a static call (read-only).
#[cfg(target_arch = "wasm32")]
pub fn static_call(address: &[u8; 32], data: &[u8], gas: u64) -> i32 {
    unsafe {
        synor_static_call(
            address.as_ptr() as i32,
            data.as_ptr() as i32,
            data.len() as i32,
            gas as i64,
        )
    }
}

/// Makes a delegate call.
#[cfg(target_arch = "wasm32")]
pub fn delegate_call(address: &[u8; 32], data: &[u8], gas: u64) -> i32 {
    unsafe {
        synor_delegate_call(
            address.as_ptr() as i32,
            data.as_ptr() as i32,
            data.len() as i32,
            gas as i64,
        )
    }
}

/// Gets the size of return data from last call.
#[cfg(target_arch = "wasm32")]
pub fn return_data_size() -> usize {
    unsafe { synor_return_data_size() as usize }
}

/// Copies return data from last call.
#[cfg(target_arch = "wasm32")]
pub fn return_data_copy(out: &mut [u8], offset: usize) {
    unsafe { synor_return_data_copy(out.as_mut_ptr() as i32, offset as i32, out.len() as i32) };
}

// ==================== Control Flow ====================

/// Reverts execution with a message.
#[cfg(target_arch = "wasm32")]
pub fn revert(msg: &str) -> ! {
    unsafe {
        synor_revert(msg.as_ptr() as i32, msg.len() as i32);
    }
    // This should never be reached
    core::arch::wasm32::unreachable()
}

/// Returns data and exits.
#[cfg(target_arch = "wasm32")]
pub fn return_data(data: &[u8]) {
    unsafe {
        synor_return(data.as_ptr() as i32, data.len() as i32);
    }
}

// ==================== Balance ====================

/// Gets the balance of an address.
#[cfg(target_arch = "wasm32")]
pub fn get_balance(address: &[u8; 34]) -> u64 {
    unsafe { synor_get_balance(address.as_ptr() as i32) as u64 }
}

/// Transfers value to an address.
#[cfg(target_arch = "wasm32")]
pub fn transfer(address: &[u8; 34], amount: u64) -> bool {
    unsafe { synor_transfer(address.as_ptr() as i32, amount as i64) == 0 }
}

// ==================== Non-WASM stubs for testing ====================

#[cfg(not(target_arch = "wasm32"))]
pub fn storage_read(_key: &[u8; 32], _out: &mut [u8]) -> i32 {
    -1
}

#[cfg(not(target_arch = "wasm32"))]
pub fn storage_write(_key: &[u8; 32], _value: &[u8]) -> i32 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn storage_delete(_key: &[u8; 32]) -> i32 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_caller() -> [u8; 34] {
    [0u8; 34]
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_address() -> [u8; 34] {
    [0u8; 34]
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_origin() -> [u8; 34] {
    [0u8; 34]
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_value() -> u64 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_calldata_size() -> usize {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_calldata(_out: &mut [u8], _offset: usize) {}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_timestamp() -> u64 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_block_height() -> u64 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_block_hash() -> [u8; 32] {
    [0u8; 32]
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_gas_remaining() -> u64 {
    u64::MAX
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_chain_id() -> u64 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn sha3(data: &[u8]) -> [u8; 32] {
    blake3::hash(data).into()
}

#[cfg(not(target_arch = "wasm32"))]
pub fn blake3(data: &[u8]) -> [u8; 32] {
    blake3::hash(data).into()
}

#[cfg(not(target_arch = "wasm32"))]
pub fn verify_ed25519(_message: &[u8], _signature: &[u8; 64], _public_key: &[u8; 32]) -> bool {
    true
}

#[cfg(not(target_arch = "wasm32"))]
pub fn emit_log(_topics: &[[u8; 32]], _data: &[u8]) -> i32 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn call(_address: &[u8; 32], _value: u64, _data: &[u8], _gas: u64) -> i32 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn static_call(_address: &[u8; 32], _data: &[u8], _gas: u64) -> i32 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn delegate_call(_address: &[u8; 32], _data: &[u8], _gas: u64) -> i32 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn return_data_size() -> usize {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn return_data_copy(_out: &mut [u8], _offset: usize) {}

#[cfg(not(target_arch = "wasm32"))]
pub fn revert(msg: &str) -> ! {
    panic!("Contract reverted: {}", msg)
}

#[cfg(not(target_arch = "wasm32"))]
pub fn return_data(_data: &[u8]) {}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_balance(_address: &[u8; 34]) -> u64 {
    0
}

#[cfg(not(target_arch = "wasm32"))]
pub fn transfer(_address: &[u8; 34], _amount: u64) -> bool {
    true
}
