//! Employment credential policy builder for WASM.

use wasm_bindgen::prelude::*;

use crate::error::LastIDError;
use crate::policies::PolicyBuilder;
use crate::types::CredentialPolicyRequest;

/// Employment credential policy builder for WASM.
///
/// # JavaScript Usage
///
/// ```javascript
/// const policy = new EmploymentPolicy()
///     .withEmployerDomain("acme.com")
///     .withStatus("Current")
///     .withFields(["company", "title"])
///     .withOptionalFields(["department", "start_date"])
///     .withCallback("https://yourapp.com/callback");
/// ```
#[wasm_bindgen(js_name = EmploymentPolicy)]
pub struct WasmEmploymentPolicy {
    callback_url: Option<String>,
    employer_domain: Option<String>,
    status: Option<String>,
    fields: Vec<String>,
    optional_fields: Vec<String>,
}

#[wasm_bindgen(js_class = EmploymentPolicy)]
impl WasmEmploymentPolicy {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        Self {
            callback_url: None,
            employer_domain: None,
            status: None,
            fields: Vec::new(),
            optional_fields: Vec::new(),
        }
    }

    /// Constrain to a specific employer domain (e.g., "example.com").
    #[wasm_bindgen(js_name = withEmployerDomain)]
    pub fn with_employer_domain(mut self, domain: String) -> Self {
        self.employer_domain = Some(domain);
        self
    }

    /// Set the required employment status: Current, Former, or Any.
    #[wasm_bindgen(js_name = withStatus)]
    pub fn with_status(mut self, status: String) -> Self {
        self.status = Some(status);
        self
    }

    /// Add required fields that must be disclosed.
    /// Valid fields: company, title, department, start_date, end_date,
    /// is_current, responsibilities
    #[wasm_bindgen(js_name = withFields)]
    pub fn with_fields(mut self, fields: Vec<String>) -> Self {
        self.fields.extend(fields);
        self
    }

    #[wasm_bindgen(js_name = withField)]
    pub fn with_field(mut self, field: String) -> Self {
        self.fields.push(field);
        self
    }

    /// Add optional fields that the user can choose to disclose.
    /// Valid fields: company, title, department, start_date, end_date,
    /// is_current, responsibilities
    #[wasm_bindgen(js_name = withOptionalFields)]
    pub fn with_optional_fields(mut self, fields: Vec<String>) -> Self {
        self.optional_fields.extend(fields);
        self
    }

    #[wasm_bindgen(js_name = withOptionalField)]
    pub fn with_optional_field(mut self, field: String) -> Self {
        self.optional_fields.push(field);
        self
    }

    #[wasm_bindgen(js_name = withCallback)]
    pub fn with_callback(mut self, url: String) -> Self {
        self.callback_url = Some(url);
        self
    }

    #[wasm_bindgen(js_name = credentialType, getter)]
    pub fn credential_type(&self) -> String {
        "LastID.Employment".to_string()
    }

    #[wasm_bindgen(js_name = isComplete, getter)]
    pub fn is_complete(&self) -> bool {
        self.callback_url.is_some()
    }
}

impl Default for WasmEmploymentPolicy {
    fn default() -> Self {
        Self::new()
    }
}

/// Map field name from JS input to snake_case for the API.
fn map_employment_field(field: &str) -> &str {
    match field {
        // Support both PascalCase (legacy) and snake_case (new)
        "Company" | "company" => "company",
        "Title" | "title" => "title",
        "Department" | "department" => "department",
        "StartDate" | "start_date" => "start_date",
        "EndDate" | "end_date" => "end_date",
        "IsCurrent" | "is_current" => "is_current",
        "Responsibilities" | "responsibilities" => "responsibilities",
        // Legacy field names (map to new names)
        "EmployerName" | "employerName" => "company",
        "JobTitle" | "jobTitle" => "title",
        "EmploymentType" | "employmentType" => "is_current",
        "WorkEmail" | "workEmail" => "company", // No direct equivalent, map to company
        other => other,
    }
}

impl PolicyBuilder for WasmEmploymentPolicy {
    fn credential_type(&self) -> &'static str {
        "LastID.Employment"
    }

    fn to_request(self) -> Result<CredentialPolicyRequest, LastIDError> {
        let callback_url = self
            .callback_url
            .ok_or_else(|| LastIDError::policy_validation("Callback URL is required"))?;

        crate::types::EndpointUrl::new(&callback_url)?;

        if let Some(ref domain) = self.employer_domain {
            if domain.is_empty() || domain.contains('@') || domain.contains('/') {
                return Err(LastIDError::policy_validation(
                    "Employer domain must be a valid domain (e.g., 'example.com')",
                ));
            }
        }

        let mut request = CredentialPolicyRequest::with_chain("LastID.Employment");
        request.callback_url = Some(callback_url);

        // Build constraints for non-field constraints (domain, status)
        let mut constraints_map = std::collections::HashMap::new();

        if let Some(domain) = &self.employer_domain {
            constraints_map.insert("employerDomain".to_string(), serde_json::json!(domain));
        }

        if let Some(status) = &self.status {
            let status_str = match status.as_str() {
                "Current" => "current",
                "Former" => "former",
                "Any" => "any",
                other => other,
            };
            constraints_map.insert(
                "employmentStatus".to_string(),
                serde_json::json!(status_str),
            );
        }

        if !constraints_map.is_empty() {
            request.constraints = Some(constraints_map);
        }

        // Set required fields (no optional flag in OpenID4VP)
        if !self.fields.is_empty() {
            request.required_fields = Some(
                self.fields
                    .iter()
                    .map(|f| map_employment_field(f).to_string())
                    .collect(),
            );
        }

        // Set optional fields (optional: true in OpenID4VP)
        if !self.optional_fields.is_empty() {
            request.optional_fields = Some(
                self.optional_fields
                    .iter()
                    .map(|f| map_employment_field(f).to_string())
                    .collect(),
            );
        }

        Ok(request)
    }
}
