//! Execution context information.
//!
//! Provides access to information about the current execution context,
//! such as the caller, block info, and transaction data.

use alloc::vec::Vec;

use crate::host;
use crate::types::{Address, Hash256};

/// Returns the address that called this contract.
///
/// For external calls, this is the transaction sender.
/// For internal calls, this is the calling contract.
pub fn caller() -> Address {
    Address(host::get_caller())
}

/// Returns the contract's own address.
pub fn self_address() -> Address {
    Address(host::get_address())
}

/// Returns the original transaction sender.
///
/// Unlike `caller()`, this always returns the externally owned account
/// that initiated the transaction, even in nested contract calls.
pub fn origin() -> Address {
    Address(host::get_origin())
}

/// Returns the value (in sompi) sent with this call.
pub fn value() -> u64 {
    host::get_value()
}

/// Returns the call data.
pub fn calldata() -> Vec<u8> {
    let size = host::get_calldata_size();
    if size == 0 {
        return Vec::new();
    }

    let mut data = alloc::vec![0u8; size];
    host::get_calldata(&mut data, 0);
    data
}

/// Returns a slice of the call data.
pub fn calldata_slice(offset: usize, length: usize) -> Vec<u8> {
    let size = host::get_calldata_size();
    if offset >= size {
        return Vec::new();
    }

    let actual_length = core::cmp::min(length, size - offset);
    let mut data = alloc::vec![0u8; actual_length];
    host::get_calldata(&mut data, offset);
    data
}

/// Returns the current block timestamp (Unix timestamp in seconds).
pub fn timestamp() -> u64 {
    host::get_timestamp()
}

/// Returns the current block height (DAA score).
pub fn block_height() -> u64 {
    host::get_block_height()
}

/// Returns the current block hash.
pub fn block_hash() -> Hash256 {
    Hash256(host::get_block_hash())
}

/// Returns the remaining gas for this execution.
pub fn gas_remaining() -> u64 {
    host::get_gas_remaining()
}

/// Returns the chain ID.
pub fn chain_id() -> u64 {
    host::get_chain_id()
}

/// Checks if this is a static (read-only) call.
///
/// Static calls cannot modify state or emit events.
pub fn is_static_call() -> bool {
    // In a static call, attempting to write storage will fail.
    // We can detect this by the chain ID being set to a special value
    // or by trying a no-op write (expensive, so avoided).
    // For now, assume not static unless VM provides a way to check.
    false
}

/// Block information structure.
#[derive(Clone, Debug)]
pub struct BlockInfo {
    /// Block height (DAA score).
    pub height: u64,
    /// Block timestamp.
    pub timestamp: u64,
    /// Block hash.
    pub hash: Hash256,
}

impl BlockInfo {
    /// Gets current block info.
    pub fn current() -> Self {
        BlockInfo {
            height: block_height(),
            timestamp: timestamp(),
            hash: block_hash(),
        }
    }
}

/// Transaction information structure.
#[derive(Clone, Debug)]
pub struct TxInfo {
    /// Transaction origin (sender).
    pub origin: Address,
    /// Value sent with transaction.
    pub value: u64,
    /// Call data.
    pub data: Vec<u8>,
}

impl TxInfo {
    /// Gets current transaction info.
    pub fn current() -> Self {
        TxInfo {
            origin: origin(),
            value: value(),
            data: calldata(),
        }
    }
}

/// Gets the balance of an address.
pub fn balance_of(address: &Address) -> u64 {
    host::get_balance(&address.0)
}

/// Gets the balance of this contract.
pub fn self_balance() -> u64 {
    let addr = self_address();
    host::get_balance(&addr.0)
}

/// Transfers value to an address.
///
/// Returns `true` on success, `false` on failure.
pub fn transfer(to: &Address, amount: u64) -> bool {
    host::transfer(&to.0, amount)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_block_info() {
        let info = BlockInfo::current();
        // Just verify it compiles and runs
        let _ = info.height;
        let _ = info.timestamp;
    }

    #[test]
    fn test_tx_info() {
        let info = TxInfo::current();
        let _ = info.origin;
        let _ = info.value;
    }
}
