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

use miden_client::account::{StorageMap, StorageSlot};
use miden_client::asset::Asset;
use miden_client::utils::Serializable;
use serde::Serialize;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::js_sys;

use crate::sync::JsAccountUpdate;

// INDEXED DB BINDINGS
// ================================================================================================

// Account IndexedDB Operations
#[wasm_bindgen(module = "/src/js/accounts.js")]
extern "C" {
    // GETS
    // --------------------------------------------------------------------------------------------

    #[wasm_bindgen(js_name = getAccountIds)]
    pub fn idxdb_get_account_ids(db_id: &str) -> js_sys::Promise;

    #[wasm_bindgen(js_name = getAllAccountHeaders)]
    pub fn idxdb_get_account_headers(db_id: &str) -> js_sys::Promise;

    #[wasm_bindgen(js_name = getAccountHeader)]
    pub fn idxdb_get_account_header(db_id: &str, account_id: String) -> js_sys::Promise;

    #[wasm_bindgen(js_name = getAccountHeaderByCommitment)]
    pub fn idxdb_get_account_header_by_commitment(
        db_id: &str,
        account_commitment: String,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = getAccountCode)]
    pub fn idxdb_get_account_code(db_id: &str, code_root: String) -> js_sys::Promise;

    #[wasm_bindgen(js_name = getAccountStorage)]
    pub fn idxdb_get_account_storage(
        db_id: &str,
        account_id: String,
        slot_names: Vec<String>,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = getAccountStorageMaps)]
    pub fn idxdb_get_account_storage_maps(db_id: &str, account_id: String) -> js_sys::Promise;

    #[wasm_bindgen(js_name = getAccountVaultAssets)]
    pub fn idxdb_get_account_vault_assets(
        db_id: &str,
        account_id: String,
        vault_keys: Vec<String>,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = getAccountAddresses)]
    pub fn idxdb_get_account_addresses(db_id: &str, account_id: String) -> js_sys::Promise;

    // INSERTS
    // --------------------------------------------------------------------------------------------

    #[wasm_bindgen(js_name = upsertAccountCode)]
    pub fn idxdb_upsert_account_code(
        db_id: &str,
        code_root: String,
        code: Vec<u8>,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = upsertAccountStorage)]
    pub fn idxdb_upsert_account_storage(
        db_id: &str,
        account_id: String,
        storage_slots: Vec<JsStorageSlot>,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = upsertStorageMapEntries)]
    pub fn idxdb_upsert_storage_map_entries(
        db_id: &str,
        account_id: String,
        entries: Vec<JsStorageMapEntry>,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = upsertVaultAssets)]
    pub fn idxdb_upsert_vault_assets(
        db_id: &str,
        account_id: String,
        assets: Vec<JsVaultAsset>,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = upsertAccountRecord)]
    pub fn idxdb_upsert_account_record(
        db_id: &str,
        id: String,
        code_root: String,
        storage_root: String,
        vault_root: String,
        nonce: String,
        committed: bool,
        commitment: String,
        account_seed: Option<Vec<u8>>,
        watched: bool,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = insertAccountAddress)]
    pub fn idxdb_insert_account_address(
        db_id: &str,
        account_id: String,
        address: Vec<u8>,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = removeAccountAddress)]
    pub fn idxdb_remove_account_address(db_id: &str, address: Vec<u8>) -> js_sys::Promise;

    #[wasm_bindgen(js_name = upsertForeignAccountCode)]
    pub fn idxdb_upsert_foreign_account_code(
        db_id: &str,
        account_id: String,
        code: Vec<u8>,
        code_root: String,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = getForeignAccountCode)]
    pub fn idxdb_get_foreign_account_code(db_id: &str, account_ids: Vec<String>)
    -> js_sys::Promise;

    // TRANSACTIONAL WRITES
    // --------------------------------------------------------------------------------------------

    #[wasm_bindgen(js_name = applyTransactionDelta)]
    pub fn idxdb_apply_transaction_delta(
        db_id: &str,
        account_id: String,
        nonce: String,
        updated_slots: Vec<JsStorageSlot>,
        changed_map_entries: Vec<JsStorageMapEntry>,
        changed_assets: Vec<JsVaultAsset>,
        code_root: String,
        storage_root: String,
        vault_root: String,
        committed: bool,
        commitment: String,
    ) -> js_sys::Promise;

    #[wasm_bindgen(js_name = applyFullAccountState)]
    pub fn idxdb_apply_full_account_state(
        db_id: &str,
        account_state: JsAccountUpdate,
    ) -> js_sys::Promise;

    // UPDATES
    // --------------------------------------------------------------------------------------------

    #[wasm_bindgen(js_name = lockAccount)]
    pub fn idxdb_lock_account(db_id: &str, account_id: String) -> js_sys::Promise;

    // DELETES
    // --------------------------------------------------------------------------------------------

    #[wasm_bindgen(js_name = undoAccountStates)]
    pub fn idxdb_undo_account_states(db_id: &str, account_hashes: Vec<String>) -> js_sys::Promise;

    /// Prunes historical account states for the specified account up to the given nonce.
    #[wasm_bindgen(js_name = pruneAccountHistory)]
    pub fn idxdb_prune_account_history(
        db_id: &str,
        account_id: String,
        up_to_nonce: String,
    ) -> js_sys::Promise;
}

// VAULT ASSET
// ================================================================================================

/// An object that contains a serialized vault asset.
///
/// `inspectable` is intentionally omitted — see the note on
/// `JsAccountUpdate` for the full rationale (#2183).
#[wasm_bindgen(getter_with_clone)]
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsVaultAsset {
    /// The vault key associated with the asset.
    #[wasm_bindgen(js_name = "vaultKey")]
    pub vault_key: String,
    /// Word representing the asset.
    #[wasm_bindgen(js_name = "asset")]
    pub asset: String,
}

impl JsVaultAsset {
    pub fn from_asset(asset: &Asset) -> Self {
        Self {
            vault_key: asset.vault_key().to_string(),
            asset: asset.to_value_word().to_hex(),
        }
    }
}

// STORAGE SLOT
// ================================================================================================

/// A JavaScript representation of a storage slot in an account.
///
/// `inspectable` is intentionally omitted — see the note on
/// `JsAccountUpdate` for the full rationale (#2183).
#[wasm_bindgen(getter_with_clone)]
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsStorageSlot {
    /// The name of the storage slot.
    #[wasm_bindgen(js_name = "slotName")]
    pub slot_name: String,
    /// The value stored in the storage slot.
    #[wasm_bindgen(js_name = "slotValue")]
    pub slot_value: String,
    /// The type of the storage slot.
    #[wasm_bindgen(js_name = "slotType")]
    pub slot_type: u8,
}

impl JsStorageSlot {
    pub fn from_slot(slot: &StorageSlot) -> Self {
        Self {
            slot_name: slot.name().to_string(),
            slot_value: slot.value().to_hex(),
            slot_type: slot.slot_type().to_bytes()[0],
        }
    }
}

// STORAGE MAP ENTRY
// ================================================================================================

/// A JavaScript representation of a storage map entry in an account.
///
/// `inspectable` is intentionally omitted — see the note on
/// `JsAccountUpdate` for the full rationale (#2183).
#[wasm_bindgen(getter_with_clone)]
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsStorageMapEntry {
    /// The slot name of the map this entry belongs to.
    #[wasm_bindgen(js_name = "slotName")]
    pub slot_name: String,
    /// The key of the storage map entry.
    #[wasm_bindgen(js_name = "key")]
    pub key: String,
    /// The value of the storage map entry.
    #[wasm_bindgen(js_name = "value")]
    pub value: String,
}

impl JsStorageMapEntry {
    pub fn from_map(map: &StorageMap, slot_name: &str) -> Vec<Self> {
        map.entries()
            .map(|(key, value)| Self {
                slot_name: slot_name.to_string(),
                key: key.to_hex(),
                value: value.to_hex(),
            })
            .collect()
    }
}
