//! WASM client wrapper.
//!
//! Provides a JavaScript-friendly interface to the LastID SDK.
//!
//! # Example
//!
//! ```javascript
//! import init, { WasmClient, WasmConfig, BaseCredentialPolicy } from 'lastid-sdk';
//!
//! await init();
//!
//! const config = new WasmConfig("https://human.dev.lastid.co", "my-client-id");
//! const client = new WasmClient(config);
//!
//! // Build policy using fluent API
//! const policy = new BaseCredentialPolicy()
//!     .withState(crypto.randomUUID())
//!     .withNonce(crypto.randomUUID())
//!     .withCallback(window.location.href);
//!
//! const response = await client.requestCredential(policy);
//! console.log("Request URI:", response.requestUri);
//! ```

#![cfg(target_arch = "wasm32")]
#![allow(clippy::future_not_send)]
#![allow(clippy::use_self)]

use wasm_bindgen::prelude::*;

use super::config::WasmConfig;
use super::error::{serialization_error, to_js_error};
use super::policies::{
    WasmAgeProofPolicy, WasmBaseCredentialPolicy, WasmEmploymentPolicy, WasmPersonaPolicy,
    WasmTrustPolicy, WasmVerifiedEmailPolicy, WasmVerifiedPersonaPolicy, WasmVerifiedPhonePolicy,
};
use crate::bindings::{
    CredentialRequestResponse, IssuerInfo, PublicKey, StatusResponse, VerifiedCredential,
};

/// JavaScript-friendly `LastID` client.
///
/// Provides methods to request credentials, poll for status, and verify
/// presentations.
#[wasm_bindgen]
pub struct WasmClient {
    inner: crate::client::LastIDClient,
}

#[wasm_bindgen]
impl WasmClient {
    /// Create a new client with the given configuration.
    ///
    /// The client will automatically manage OAuth token acquisition and
    /// refresh.
    ///
    /// @param config - The SDK configuration
    /// @throws Error if the configuration is invalid
    #[wasm_bindgen(constructor)]
    pub fn new(config: &WasmConfig) -> Result<WasmClient, JsError> {
        let inner = crate::client::LastIDClient::new(config.inner.clone()).map_err(to_js_error)?;
        Ok(WasmClient { inner })
    }

    /// Request a credential using a policy builder.
    ///
    /// Use the fluent API policy builders (e.g., `BaseCredentialPolicy`) to
    /// create a policy, then pass it to this method.
    ///
    /// @param policy - A policy builder (e.g., BaseCredentialPolicy)
    /// @returns Response with requestId, requestUri (for QR code), expiresIn,
    /// and nonce @throws Error if the policy is invalid or the request
    /// fails
    ///
    /// @example
    /// ```javascript
    /// const policy = new BaseCredentialPolicy()
    ///     .withState(crypto.randomUUID())
    ///     .withNonce(crypto.randomUUID())
    ///     .withCallback("https://myapp.com/callback");
    ///
    /// const response = await client.requestCredential(policy);
    /// console.log(response.requestUri); // Use for QR code
    /// ```
    #[wasm_bindgen(js_name = requestCredential)]
    pub async fn request_credential(
        &self,
        policy: WasmBaseCredentialPolicy,
    ) -> Result<JsValue, JsError> {
        // Use the SDK's request_credential method with the policy builder
        let response = self
            .inner
            .request_credential(policy)
            .await
            .map_err(to_js_error)?;

        // Return the full response as JS object
        let js_response = CredentialRequestResponse {
            request_id: response.request_id.to_string(),
            request_uri: response.request_uri,
            expires_in: response.expires_in,
            nonce: response.nonce,
        };

        serde_wasm_bindgen::to_value(&js_response).map_err(serialization_error)
    }

    /// Request a persona credential with optional field selection.
    #[wasm_bindgen(js_name = requestPersonaCredential)]
    pub async fn request_persona_credential(
        &self,
        policy: WasmPersonaPolicy,
    ) -> Result<JsValue, JsError> {
        let response = self
            .inner
            .request_credential(policy)
            .await
            .map_err(to_js_error)?;

        let js_response = CredentialRequestResponse {
            request_id: response.request_id.to_string(),
            request_uri: response.request_uri,
            expires_in: response.expires_in,
            nonce: response.nonce,
        };

        serde_wasm_bindgen::to_value(&js_response).map_err(serialization_error)
    }

    /// Request a verified email credential with optional domain constraint.
    #[wasm_bindgen(js_name = requestVerifiedEmailCredential)]
    pub async fn request_verified_email_credential(
        &self,
        policy: WasmVerifiedEmailPolicy,
    ) -> Result<JsValue, JsError> {
        let response = self
            .inner
            .request_credential(policy)
            .await
            .map_err(to_js_error)?;

        let js_response = CredentialRequestResponse {
            request_id: response.request_id.to_string(),
            request_uri: response.request_uri,
            expires_in: response.expires_in,
            nonce: response.nonce,
        };

        serde_wasm_bindgen::to_value(&js_response).map_err(serialization_error)
    }

    /// Request a verified phone credential with optional country code
    /// constraint.
    #[wasm_bindgen(js_name = requestVerifiedPhoneCredential)]
    pub async fn request_verified_phone_credential(
        &self,
        policy: WasmVerifiedPhonePolicy,
    ) -> Result<JsValue, JsError> {
        let response = self
            .inner
            .request_credential(policy)
            .await
            .map_err(to_js_error)?;

        let js_response = CredentialRequestResponse {
            request_id: response.request_id.to_string(),
            request_uri: response.request_uri,
            expires_in: response.expires_in,
            nonce: response.nonce,
        };

        serde_wasm_bindgen::to_value(&js_response).map_err(serialization_error)
    }

    /// Request a verified persona (KYC) credential.
    #[wasm_bindgen(js_name = requestVerifiedPersonaCredential)]
    pub async fn request_verified_persona_credential(
        &self,
        policy: WasmVerifiedPersonaPolicy,
    ) -> Result<JsValue, JsError> {
        let response = self
            .inner
            .request_credential(policy)
            .await
            .map_err(to_js_error)?;

        let js_response = CredentialRequestResponse {
            request_id: response.request_id.to_string(),
            request_uri: response.request_uri,
            expires_in: response.expires_in,
            nonce: response.nonce,
        };

        serde_wasm_bindgen::to_value(&js_response).map_err(serialization_error)
    }

    /// Request an employment credential.
    #[wasm_bindgen(js_name = requestEmploymentCredential)]
    pub async fn request_employment_credential(
        &self,
        policy: WasmEmploymentPolicy,
    ) -> Result<JsValue, JsError> {
        let response = self
            .inner
            .request_credential(policy)
            .await
            .map_err(to_js_error)?;

        let js_response = CredentialRequestResponse {
            request_id: response.request_id.to_string(),
            request_uri: response.request_uri,
            expires_in: response.expires_in,
            nonce: response.nonce,
        };

        serde_wasm_bindgen::to_value(&js_response).map_err(serialization_error)
    }

    /// Request a trust credential.
    #[wasm_bindgen(js_name = requestTrustCredential)]
    pub async fn request_trust_credential(
        &self,
        policy: WasmTrustPolicy,
    ) -> Result<JsValue, JsError> {
        let response = self
            .inner
            .request_credential(policy)
            .await
            .map_err(to_js_error)?;

        let js_response = CredentialRequestResponse {
            request_id: response.request_id.to_string(),
            request_uri: response.request_uri,
            expires_in: response.expires_in,
            nonce: response.nonce,
        };

        serde_wasm_bindgen::to_value(&js_response).map_err(serialization_error)
    }

    /// Request an age proof credential.
    #[wasm_bindgen(js_name = requestAgeProofCredential)]
    pub async fn request_age_proof_credential(
        &self,
        policy: WasmAgeProofPolicy,
    ) -> Result<JsValue, JsError> {
        let response = self
            .inner
            .request_credential(policy)
            .await
            .map_err(to_js_error)?;

        let js_response = CredentialRequestResponse {
            request_id: response.request_id.to_string(),
            request_uri: response.request_uri,
            expires_in: response.expires_in,
            nonce: response.nonce,
        };

        serde_wasm_bindgen::to_value(&js_response).map_err(serialization_error)
    }

    /// Poll for request status.
    ///
    /// Returns the status as a JSON object.
    #[wasm_bindgen(js_name = pollStatus)]
    pub async fn poll_status(&self, request_id: &str) -> Result<JsValue, JsError> {
        let rid = crate::types::RequestId::new(request_id.to_string());
        let status = self.inner.poll_request(&rid).await.map_err(to_js_error)?;

        let status_obj = StatusResponse::from(status);
        serde_wasm_bindgen::to_value(&status_obj).map_err(serialization_error)
    }

    /// Subscribe for credential request completion via WebSocket.
    ///
    /// Uses WebSocket transport for instant status updates, with automatic
    /// fallback to HTTP polling if WebSocket connection fails.
    ///
    /// Returns the status as a JSON object when complete.
    #[cfg(feature = "websocket")]
    #[wasm_bindgen(js_name = subscribeForCompletion)]
    pub async fn subscribe_for_completion(&self, request_id: &str) -> Result<JsValue, JsError> {
        let rid = crate::types::RequestId::new(request_id.to_string());
        let status = self
            .inner
            .subscribe_for_completion(&rid)
            .await
            .map_err(to_js_error)?;

        let status_obj = StatusResponse::from(status);
        serde_wasm_bindgen::to_value(&status_obj).map_err(serialization_error)
    }

    /// Verify a presentation string.
    ///
    /// Returns the verified credential as a JSON object.
    #[wasm_bindgen(js_name = verifyPresentation)]
    pub async fn verify_presentation(&self, presentation: &str) -> Result<JsValue, JsError> {
        let verified = self
            .inner
            .verify_presentation(presentation)
            .await
            .map_err(to_js_error)?;

        let response = VerifiedCredential {
            subject_did: verified.subject_did,
            issuer_did: verified.issuer_did,
            credential_type: verified.credential_type,
            claims: verified.claims,
            issued_at: verified.issued_at.to_rfc3339(),
            expires_at: verified.expires_at.to_rfc3339(),
            issuer_status: format!("{:?}", verified.issuer_status),
            signature_verified: verified.signature_verified,
        };

        serde_wasm_bindgen::to_value(&response).map_err(serialization_error)
    }

    /// Validate an issuer DID against the trust registry.
    #[wasm_bindgen(js_name = validateIssuer)]
    pub async fn validate_issuer(&self, issuer_did: &str) -> Result<JsValue, JsError> {
        let info = self
            .inner
            .validate_issuer(issuer_did)
            .await
            .map_err(to_js_error)?;

        let response = IssuerInfo {
            enterprise_did: info.enterprise_did,
            organization_name: info.organization_name,
            endpoint_url: info.endpoint_url,
            status: format!("{:?}", info.status),
            permitted_types: info.permitted_types,
            public_key: PublicKey {
                kty: info.public_key.kty,
                crv: info.public_key.crv,
                x: info.public_key.x,
                y: info.public_key.y,
            },
        };

        serde_wasm_bindgen::to_value(&response).map_err(serialization_error)
    }
}
