//! Tauri commands for managing custom XBRL concept mappings.

use tauri::AppHandle;

use crate::mappings::{
    CreateCustomConceptMappingRequest, CustomConceptMapping, FormulaValidationResult,
    MappingChangeKind, MappingsConfig, MappingsUpdatedEvent,
};
use crate::state::AppState;
use crate::terminal::types::AvailableXbrlConcept;

/// List all custom mappings.
#[tauri::command]
pub async fn list_custom_mappings(
    state: tauri::State<'_, AppState>,
) -> Result<Vec<CustomConceptMapping>, String> {
    state
        .mappings_service
        .list_mappings()
        .await
        .map_err(|e| e.to_string())
}

/// Get a single mapping by ID.
#[tauri::command]
pub async fn get_custom_mapping(
    id: String,
    state: tauri::State<'_, AppState>,
) -> Result<CustomConceptMapping, String> {
    state
        .mappings_service
        .get_mapping(&id)
        .map_err(|e| e.to_string())
}

/// Add a new custom mapping.
#[tauri::command]
pub async fn add_custom_mapping(
    mapping: CustomConceptMapping,
    app: AppHandle,
    state: tauri::State<'_, AppState>,
) -> Result<String, String> {
    let request = CreateCustomConceptMappingRequest {
        target_field: mapping.target_field,
        mapping_kind: mapping.mapping_kind,
        scope: mapping.scope,
        notes: mapping.notes,
    };
    let (saved_mapping, was_update) = state
        .mappings_service
        .create_or_update_mapping(request)
        .await
        .map_err(|e| e.to_string())?;
    let mapping_id = saved_mapping.id.clone();
    let event = MappingsUpdatedEvent::for_mapping(
        if was_update {
            MappingChangeKind::Updated
        } else {
            MappingChangeKind::Created
        },
        &saved_mapping,
    );
    emit_mapping_update(&app, event);
    Ok(mapping_id)
}

/// Update an existing custom mapping.
#[tauri::command]
pub async fn update_custom_mapping(
    id: String,
    mapping: CustomConceptMapping,
    app: AppHandle,
    state: tauri::State<'_, AppState>,
) -> Result<(), String> {
    let previous = state
        .mappings_service
        .get_mapping(&id)
        .map_err(|e| e.to_string())?;
    let event = MappingsUpdatedEvent::for_updated(&previous, &mapping);
    state
        .mappings_service
        .update_mapping(&id, mapping)
        .await
        .map_err(|e| e.to_string())?;
    emit_mapping_update(&app, event);
    Ok(())
}

/// Delete a custom mapping.
#[tauri::command]
pub async fn delete_custom_mapping(
    id: String,
    app: AppHandle,
    state: tauri::State<'_, AppState>,
) -> Result<(), String> {
    let existing = state
        .mappings_service
        .get_mapping(&id)
        .map_err(|e| e.to_string())?;
    state
        .mappings_service
        .delete_mapping(&id)
        .await
        .map_err(|e| e.to_string())?;
    emit_mapping_update(
        &app,
        MappingsUpdatedEvent::for_mapping(crate::mappings::MappingChangeKind::Deleted, &existing),
    );
    Ok(())
}

/// Toggle the enabled state of a custom mapping.
#[tauri::command]
pub async fn toggle_custom_mapping(
    id: String,
    app: AppHandle,
    state: tauri::State<'_, AppState>,
) -> Result<bool, String> {
    let existing = state
        .mappings_service
        .get_mapping(&id)
        .map_err(|e| e.to_string())?;
    let enabled = state
        .mappings_service
        .toggle_mapping(&id)
        .await
        .map_err(|e| e.to_string())?;
    emit_mapping_update(
        &app,
        MappingsUpdatedEvent::for_mapping(crate::mappings::MappingChangeKind::Toggled, &existing),
    );
    Ok(enabled)
}

/// Import mappings from JSON.
#[tauri::command]
pub async fn import_mappings(
    json: String,
    app: AppHandle,
    state: tauri::State<'_, AppState>,
) -> Result<MappingsConfig, String> {
    let config = state
        .mappings_service
        .import_mappings(&json)
        .await
        .map_err(|e| e.to_string())?;
    emit_mapping_update(&app, MappingsUpdatedEvent::for_imported(&config.mappings));
    Ok(config)
}

/// Export mappings to JSON.
#[tauri::command]
pub async fn export_mappings(state: tauri::State<'_, AppState>) -> Result<String, String> {
    state
        .mappings_service
        .export_mappings()
        .await
        .map_err(|e| e.to_string())
}

/// Validate a formula expression without creating a mapping.
#[tauri::command]
pub async fn validate_formula(
    expression: String,
    state: tauri::State<'_, AppState>,
) -> Result<FormulaValidationResult, String> {
    state
        .mappings_service
        .validate_formula(&expression)
        .map_err(|e| e.to_string())
}

/// Get available XBRL concepts for formula autocomplete.
///
/// When a non-empty ticker is provided, loads concepts from the most recent
/// SEC filing for that ticker (cached after first fetch). Falls back to a
/// curated list of common concepts when the ticker is empty or loading fails.
#[tauri::command]
pub async fn get_ticker_concepts(
    ticker: String,
    state: tauri::State<'_, AppState>,
) -> Result<Vec<AvailableXbrlConcept>, String> {
    let ticker_upper = ticker.trim().to_uppercase();

    if !ticker_upper.is_empty() {
        if let Some(cached) = state
            .concept_cache
            .lock()
            .map(|cache| cache.get(&ticker_upper).cloned())
            .ok()
            .flatten()
        {
            return Ok(cached);
        }

        match state
            .edgar_lookup
            .load_concepts_for_ticker(&ticker_upper)
            .await
        {
            Ok(concepts) => {
                if let Ok(mut cache) = state.concept_cache.lock() {
                    cache.insert(ticker_upper, concepts.clone());
                }
                Ok(concepts)
            }
            Err(_) => Ok(get_common_concepts()),
        }
    } else {
        let mut merged = get_common_concepts();
        if let Ok(cache) = state.concept_cache.lock() {
            let mut seen = merged
                .iter()
                .map(|c| (c.taxonomy.clone(), c.concept.clone(), c.unit_family.clone()))
                .collect::<std::collections::HashSet<_>>();

            for (_, ticker_concepts) in cache.iter() {
                for concept in ticker_concepts {
                    if seen.insert((
                        concept.taxonomy.clone(),
                        concept.concept.clone(),
                        concept.unit_family.clone(),
                    )) {
                        merged.push(concept.clone());
                    }
                }
            }
        }
        Ok(merged)
    }
}

/// Returns a curated list of common XBRL concepts for formula autocomplete.
fn get_common_concepts() -> Vec<AvailableXbrlConcept> {
    static COMMON_CONCEPTS: std::sync::LazyLock<Vec<AvailableXbrlConcept>> =
        std::sync::LazyLock::new(|| {
            vec![
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "Revenue".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("Revenue".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "GrossProfit".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("Gross Profit".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "OperatingIncomeLoss".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("Operating Income".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "NetIncomeLoss".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("Net Income".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "CostOfGoodsAndServicesSold".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("Cost of Goods Sold".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "SellingGeneralAndAdministrativeExpense".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("SG&A Expense".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "ResearchAndDevelopmentExpense".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("R&D Expense".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "Assets".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("Total Assets".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "CashAndCashEquivalentsAtCarryingValue".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("Cash and Cash Equivalents".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "Liabilities".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("Total Liabilities".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "StockholdersEquity".to_string(),
                    unit_family: "currency".to_string(),
                    label: Some("Stockholders Equity".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "EarningsPerShareBasic".to_string(),
                    unit_family: "currency_per_share".to_string(),
                    label: Some("Basic EPS".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "us-gaap".to_string(),
                    concept: "EarningsPerShareDiluted".to_string(),
                    unit_family: "currency_per_share".to_string(),
                    label: Some("Diluted EPS".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
                AvailableXbrlConcept {
                    taxonomy: "dei".to_string(),
                    concept: "EntityCommonStockSharesOutstanding".to_string(),
                    unit_family: "shares".to_string(),
                    label: Some("Shares Outstanding".to_string()),
                    period_id: "common".to_string(),
                    value: None,
                },
            ]
        });
    COMMON_CONCEPTS.clone()
}

fn emit_mapping_update(app: &AppHandle, payload: MappingsUpdatedEvent) {
    if let Err(error) = crate::mappings::emit_mappings_updated(app, &payload) {
        eprintln!("Failed to emit mappings_updated event: {error}");
    }
}
