use serde_json::{json, Value};

use crate::research::{
    ArchiveResearchNoteRequest, ArchiveResearchWorkspaceRequest, CaptureResearchNoteRequest,
    CreateFinancialModelRequest, CreateResearchWorkspaceRequest, EvaluateFinancialModelRequest,
    EvaluateFinancialModelResult, ExportFinancialModelXlsxRequest, ExportFinancialModelXlsxResult,
    ExportResearchBundleRequest, FinancialModel, GetFinancialModelRequest,
    GetNoteAuditTrailRequest, GetNotesByTickerRequest, GetResearchFileTextPreviewRequest,
    GetWorkspaceProjectionRequest, GhostNote, LinkNoteToModelCellRequest,
    ListFinancialModelsRequest, ListModelCellAnnotationsRequest, ListModelDataBindingsRequest,
    ListNoteLinksRequest, ListResearchFilesRequest, ListResearchJobsRequest,
    ListResearchNotesRequest, ListWorkspaceGhostNotesRequest, ModelBindingRefreshPolicy,
    ModelCellAnnotation, ModelDataBinding, ModelDataBindingKind, NoteAuditTrail, NoteCaptureResult,
    NoteLink, PipelineJob, PreviewLinkRequest, PreviewLinkResult, PromoteNoteToThesisRequest,
    RefreshModelDataBindingsRequest, ResearchBundleExport, ResearchFile, ResearchFileTextPreview,
    ResearchNote, ResearchWorkspace, ResolveGhostInterventionRequest, RetryResearchJobsRequest,
    ReviewGhostNoteRequest, SaveFinancialModelRequest, SaveModelDataBindingRequest,
    SearchResearchNotesRequest, UnlinkNoteFromModelCellRequest, UpdateModelCellAnnotationRequest,
    UpdateResearchNoteRequest, UpdateResearchWorkspaceRequest, UploadResearchFilesRequest,
    WorkspaceProjection,
};
use crate::state::AppState;
use crate::terminal::sec_edgar::{BalanceSheetQuery, CashFlowQuery, EdgarDataLookup};
use crate::terminal::{
    BalanceSheetPanelData, CashFlowPanelData, Company, FinancialsPanelData, Frequency,
};

#[tauri::command]
pub async fn create_research_workspace(
    state: tauri::State<'_, AppState>,
    request: CreateResearchWorkspaceRequest,
) -> Result<ResearchWorkspace, String> {
    state
        .research_service
        .create_workspace(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn list_research_workspaces(
    state: tauri::State<'_, AppState>,
) -> Result<Vec<ResearchWorkspace>, String> {
    state
        .research_service
        .list_workspaces()
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn get_research_workspace(
    state: tauri::State<'_, AppState>,
    workspace_id: String,
) -> Result<ResearchWorkspace, String> {
    state
        .research_service
        .get_workspace(&workspace_id)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn capture_research_note(
    state: tauri::State<'_, AppState>,
    request: CaptureResearchNoteRequest,
) -> Result<NoteCaptureResult, String> {
    state
        .research_service
        .capture_note(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn preview_note_link(
    state: tauri::State<'_, AppState>,
    request: PreviewLinkRequest,
) -> Result<PreviewLinkResult, String> {
    state
        .research_service
        .preview_note_link(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn update_research_note(
    state: tauri::State<'_, AppState>,
    request: UpdateResearchNoteRequest,
) -> Result<ResearchNote, String> {
    state
        .research_service
        .update_note(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn archive_research_note(
    state: tauri::State<'_, AppState>,
    request: ArchiveResearchNoteRequest,
) -> Result<ResearchNote, String> {
    state
        .research_service
        .archive_note(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn list_research_notes(
    state: tauri::State<'_, AppState>,
    request: ListResearchNotesRequest,
) -> Result<Vec<ResearchNote>, String> {
    state
        .research_service
        .list_notes(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn get_notes_by_ticker(
    state: tauri::State<'_, AppState>,
    request: GetNotesByTickerRequest,
) -> Result<Vec<ResearchNote>, String> {
    state
        .research_service
        .get_notes_by_ticker(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn get_workspace_projection(
    state: tauri::State<'_, AppState>,
    request: GetWorkspaceProjectionRequest,
) -> Result<WorkspaceProjection, String> {
    state
        .research_service
        .get_workspace_projection(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn list_financial_models(
    state: tauri::State<'_, AppState>,
    request: ListFinancialModelsRequest,
) -> Result<Vec<FinancialModel>, String> {
    state
        .research_service
        .list_financial_models(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn get_financial_model(
    state: tauri::State<'_, AppState>,
    request: GetFinancialModelRequest,
) -> Result<FinancialModel, String> {
    state
        .research_service
        .get_financial_model(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn create_financial_model(
    state: tauri::State<'_, AppState>,
    request: CreateFinancialModelRequest,
) -> Result<FinancialModel, String> {
    state
        .research_service
        .create_financial_model(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn save_financial_model(
    state: tauri::State<'_, AppState>,
    request: SaveFinancialModelRequest,
) -> Result<FinancialModel, String> {
    state
        .research_service
        .save_financial_model(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn evaluate_financial_model(
    state: tauri::State<'_, AppState>,
    request: EvaluateFinancialModelRequest,
) -> Result<EvaluateFinancialModelResult, String> {
    state
        .research_service
        .evaluate_financial_model(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn list_model_data_bindings(
    state: tauri::State<'_, AppState>,
    request: ListModelDataBindingsRequest,
) -> Result<Vec<ModelDataBinding>, String> {
    state
        .research_service
        .list_model_data_bindings(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn save_model_data_binding(
    state: tauri::State<'_, AppState>,
    request: SaveModelDataBindingRequest,
) -> Result<ModelDataBinding, String> {
    state
        .research_service
        .save_model_data_binding(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn refresh_model_data_bindings(
    state: tauri::State<'_, AppState>,
    request: RefreshModelDataBindingsRequest,
) -> Result<Vec<ModelDataBinding>, String> {
    let mut model = state
        .research_service
        .get_financial_model(GetFinancialModelRequest {
            model_id: request.model_id.clone(),
        })
        .await
        .map_err(|error| error.to_string())?;
    let mut bindings = state
        .research_service
        .list_model_data_bindings(ListModelDataBindingsRequest {
            model_id: request.model_id.clone(),
        })
        .await
        .map_err(|error| error.to_string())?;
    bindings.extend(infer_workbook_bindings(&model));

    if !request.binding_ids.is_empty() {
        let ids = request
            .binding_ids
            .iter()
            .cloned()
            .collect::<std::collections::HashSet<_>>();
        bindings.retain(|binding| ids.contains(&binding.id));
    }

    let ticker = model
        .ticker
        .clone()
        .ok_or_else(|| "model has no ticker for data binding refresh".to_string())?;
    let mut sec_financials: Option<FinancialsPanelData> = None;
    let mut sec_balance_sheet: Option<BalanceSheetPanelData> = None;
    let mut sec_cash_flow: Option<CashFlowPanelData> = None;
    let mut market_snapshot: Option<Company> = None;
    let mut refreshed = Vec::new();
    let now = command_now_rfc3339();

    for mut binding in bindings {
        match binding.kind {
            ModelDataBindingKind::SecEdgar | ModelDataBindingKind::Mapping => {
                if sec_field_uses_cash_flow(&binding.source_key) && sec_cash_flow.is_none() {
                    sec_cash_flow = Some(
                        state
                            .edgar_lookup
                            .cash_flow(
                                binding_source_ticker(&binding, &ticker).as_str(),
                                CashFlowQuery {
                                    frequency: Frequency::Annual,
                                    range: None,
                                },
                            )
                            .await
                            .map_err(|error| error.to_string())?,
                    );
                }
                if sec_field_uses_balance_sheet(&binding.source_key) && sec_balance_sheet.is_none()
                {
                    sec_balance_sheet = Some(
                        state
                            .edgar_lookup
                            .balance_sheet(
                                binding_source_ticker(&binding, &ticker).as_str(),
                                BalanceSheetQuery {
                                    frequency: Frequency::Annual,
                                    range: None,
                                    requested_currency: None,
                                },
                            )
                            .await
                            .map_err(|error| error.to_string())?,
                    );
                }
                if !sec_field_uses_cash_flow(&binding.source_key)
                    && !sec_field_uses_balance_sheet(&binding.source_key)
                    && sec_financials.is_none()
                {
                    sec_financials = Some(
                        state
                            .edgar_lookup
                            .financials(
                                binding_source_ticker(&binding, &ticker).as_str(),
                                Frequency::Annual,
                            )
                            .await
                            .map_err(|error| error.to_string())?,
                    );
                }
                let values = sec_binding_values(
                    &binding.source_key,
                    sec_financials.as_ref(),
                    sec_balance_sheet.as_ref(),
                    sec_cash_flow.as_ref(),
                );
                write_binding_values(&mut model.workbook_json, &binding, &values);
            }
            ModelDataBindingKind::Market => {
                if market_snapshot.is_none() {
                    market_snapshot = Some(
                        state
                            .command_service
                            .lookup_company(binding_source_ticker(&binding, &ticker).as_str())
                            .await?,
                    );
                }
                let values = market_binding_values(&binding.source_key, market_snapshot.as_ref());
                write_binding_values(&mut model.workbook_json, &binding, &values);
            }
        }

        binding.last_refresh_at = Some(now.clone());
        binding.updated_at = now.clone();
        if !binding.id.starts_with("inferred-") {
            refreshed.push(
                state
                    .research_service
                    .save_model_data_binding(SaveModelDataBindingRequest { binding })
                    .await
                    .map_err(|error| error.to_string())?,
            );
        } else {
            refreshed.push(binding);
        }
    }

    state
        .research_service
        .save_financial_model(SaveFinancialModelRequest {
            model_id: model.id,
            workbook_json: model.workbook_json,
            annotations: model.annotations,
        })
        .await
        .map_err(|error| error.to_string())?;

    Ok(refreshed)
}

#[tauri::command]
pub async fn link_note_to_model_cell(
    state: tauri::State<'_, AppState>,
    request: LinkNoteToModelCellRequest,
) -> Result<ModelCellAnnotation, String> {
    state
        .research_service
        .link_note_to_model_cell(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn unlink_note_from_model_cell(
    state: tauri::State<'_, AppState>,
    request: UnlinkNoteFromModelCellRequest,
) -> Result<ModelCellAnnotation, String> {
    state
        .research_service
        .unlink_note_from_model_cell(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn update_model_cell_annotation(
    state: tauri::State<'_, AppState>,
    request: UpdateModelCellAnnotationRequest,
) -> Result<ModelCellAnnotation, String> {
    state
        .research_service
        .update_model_cell_annotation(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn list_model_cell_annotations(
    state: tauri::State<'_, AppState>,
    request: ListModelCellAnnotationsRequest,
) -> Result<Vec<ModelCellAnnotation>, String> {
    state
        .research_service
        .list_model_cell_annotations(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn export_financial_model_xlsx(
    state: tauri::State<'_, AppState>,
    request: ExportFinancialModelXlsxRequest,
) -> Result<ExportFinancialModelXlsxResult, String> {
    state
        .research_service
        .export_financial_model_xlsx(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn list_note_links(
    state: tauri::State<'_, AppState>,
    request: ListNoteLinksRequest,
) -> Result<Vec<NoteLink>, String> {
    state
        .research_service
        .list_note_links(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn list_workspace_ghost_notes(
    state: tauri::State<'_, AppState>,
    request: ListWorkspaceGhostNotesRequest,
) -> Result<Vec<GhostNote>, String> {
    state
        .research_service
        .list_workspace_ghost_notes(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn review_ghost_note(
    state: tauri::State<'_, AppState>,
    request: ReviewGhostNoteRequest,
) -> Result<GhostNote, String> {
    state
        .research_service
        .review_ghost_note(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn resolve_ghost_intervention(
    state: tauri::State<'_, AppState>,
    request: ResolveGhostInterventionRequest,
) -> Result<GhostNote, String> {
    state
        .research_service
        .resolve_ghost_intervention(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn promote_note_to_thesis(
    state: tauri::State<'_, AppState>,
    request: PromoteNoteToThesisRequest,
) -> Result<ResearchNote, String> {
    state
        .research_service
        .promote_note_to_thesis(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn retry_research_jobs(
    state: tauri::State<'_, AppState>,
    request: RetryResearchJobsRequest,
) -> Result<Vec<PipelineJob>, String> {
    state
        .research_service
        .retry_research_jobs(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn list_research_jobs(
    state: tauri::State<'_, AppState>,
    request: ListResearchJobsRequest,
) -> Result<Vec<PipelineJob>, String> {
    state
        .research_service
        .list_jobs(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn get_note_audit_trail(
    state: tauri::State<'_, AppState>,
    request: GetNoteAuditTrailRequest,
) -> Result<NoteAuditTrail, String> {
    state
        .research_service
        .get_note_audit_trail(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn export_research_bundle(
    state: tauri::State<'_, AppState>,
    request: ExportResearchBundleRequest,
) -> Result<ResearchBundleExport, String> {
    state
        .research_service
        .export_research_bundle(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn search_research_notes(
    state: tauri::State<'_, AppState>,
    request: SearchResearchNotesRequest,
) -> Result<Vec<ResearchNote>, String> {
    state
        .research_service
        .search_notes(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn update_research_workspace(
    state: tauri::State<'_, AppState>,
    request: UpdateResearchWorkspaceRequest,
) -> Result<ResearchWorkspace, String> {
    state
        .research_service
        .update_workspace(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn archive_research_workspace(
    state: tauri::State<'_, AppState>,
    request: ArchiveResearchWorkspaceRequest,
) -> Result<ResearchWorkspace, String> {
    state
        .research_service
        .archive_workspace(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn list_research_files(
    state: tauri::State<'_, AppState>,
    request: ListResearchFilesRequest,
) -> Result<Vec<ResearchFile>, String> {
    state
        .research_service
        .list_files(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn upload_research_files(
    state: tauri::State<'_, AppState>,
    request: UploadResearchFilesRequest,
) -> Result<Vec<ResearchFile>, String> {
    state
        .research_service
        .upload_files(request)
        .await
        .map_err(|error| error.to_string())
}

#[tauri::command]
pub async fn get_research_file_text_preview(
    state: tauri::State<'_, AppState>,
    request: GetResearchFileTextPreviewRequest,
) -> Result<ResearchFileTextPreview, String> {
    state
        .research_service
        .get_file_text_preview(request)
        .await
        .map_err(|error| error.to_string())
}

fn infer_workbook_bindings(model: &FinancialModel) -> Vec<ModelDataBinding> {
    let now = command_now_rfc3339();
    let mut bindings = Vec::new();
    let Some(sheets) = model.workbook_json.get("sheets").and_then(Value::as_array) else {
        return bindings;
    };

    for sheet in sheets {
        let sheet_id = sheet
            .get("id")
            .and_then(Value::as_str)
            .unwrap_or("sheet1")
            .to_string();
        let Some(cells) = sheet.get("cells").and_then(Value::as_object) else {
            continue;
        };
        for (address, cell) in cells {
            let Some(binding) = cell.get("binding") else {
                continue;
            };
            let Some(kind) = binding.get("kind").and_then(Value::as_str) else {
                continue;
            };
            let Some(field) = binding.get("field").and_then(Value::as_str) else {
                continue;
            };
            let Some((_, row)) = parse_cell_address(address) else {
                continue;
            };
            let kind = match kind {
                "sec_edgar" => ModelDataBindingKind::SecEdgar,
                "mapping" => ModelDataBindingKind::Mapping,
                "market" => ModelDataBindingKind::Market,
                _ => continue,
            };
            let historical_range = inferred_historical_range(&model.workbook_json, row);
            bindings.push(ModelDataBinding {
                id: format!("inferred-{}-{}-{}", model.id, sheet_id, address),
                model_id: model.id.clone(),
                sheet_id: sheet_id.clone(),
                range: historical_range,
                kind,
                source_key: field.to_string(),
                refresh_policy: ModelBindingRefreshPolicy::Manual,
                last_refresh_at: None,
                created_at: now.clone(),
                updated_at: now.clone(),
            });
        }
    }
    bindings
}

fn inferred_historical_range(workbook_json: &Value, row: usize) -> String {
    let historical_years = workbook_json
        .get("metadata")
        .and_then(|metadata| metadata.get("mosaiciq"))
        .filter(|metadata| {
            metadata.get("template").and_then(Value::as_str) == Some("three_statement")
        })
        .and_then(|metadata| metadata.get("historicalYears"))
        .and_then(Value::as_u64)
        .and_then(|years| usize::try_from(years).ok())
        .filter(|years| *years > 0)
        .unwrap_or(5);
    let end_col = column_name(historical_years);
    format!("B{row}:{end_col}{row}")
}

fn command_now_rfc3339() -> String {
    chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
}

fn binding_source_ticker(binding: &ModelDataBinding, default_ticker: &str) -> String {
    binding
        .source_key
        .split_once(':')
        .map(|(ticker, _)| ticker.trim().to_ascii_uppercase())
        .filter(|ticker| !ticker.is_empty())
        .unwrap_or_else(|| default_ticker.to_ascii_uppercase())
}

fn binding_field(source_key: &str) -> &str {
    source_key
        .split_once(':')
        .map(|(_, field)| field)
        .unwrap_or(source_key)
        .trim()
}

fn sec_field_uses_cash_flow(source_key: &str) -> bool {
    matches!(
        binding_field(source_key),
        "free_cash_flow" | "operating_cash_flow" | "capex" | "capital_expenditure"
    )
}

fn sec_field_uses_balance_sheet(source_key: &str) -> bool {
    matches!(
        binding_field(source_key),
        "cash_and_equivalents"
            | "short_term_investments"
            | "accounts_receivable_net"
            | "inventory"
            | "other_current_assets"
            | "total_current_assets"
            | "property_plant_and_equipment_net"
            | "goodwill"
            | "intangible_assets"
            | "other_non_current_assets"
            | "total_assets"
            | "accounts_payable"
            | "accrued_expenses_and_other_current_liabilities"
            | "short_term_debt"
            | "total_current_liabilities"
            | "long_term_debt"
            | "other_non_current_liabilities"
            | "total_liabilities"
            | "retained_earnings"
            | "accumulated_other_comprehensive_income_loss"
            | "total_equity"
            | "total_shares_outstanding"
            | "shares_outstanding"
    )
}

fn sec_binding_values(
    source_key: &str,
    financials: Option<&FinancialsPanelData>,
    balance_sheet: Option<&BalanceSheetPanelData>,
    cash_flow: Option<&CashFlowPanelData>,
) -> Vec<Option<f64>> {
    let field = binding_field(source_key);
    if let Some(cash_flow) = cash_flow {
        let mut periods = cash_flow.periods.clone();
        periods.sort_by(|left, right| left.period_end.cmp(&right.period_end));
        return periods
            .into_iter()
            .rev()
            .take(5)
            .collect::<Vec<_>>()
            .into_iter()
            .rev()
            .map(|period| match field {
                "free_cash_flow" => period.free_cash_flow,
                "operating_cash_flow" => period.operating_cash_flow,
                "capex" | "capital_expenditure" => period.capex,
                _ => None,
            })
            .collect();
    }

    if let Some(balance_sheet) = balance_sheet {
        let mut periods = balance_sheet.periods.clone();
        periods.sort_by(|left, right| left.period_end.cmp(&right.period_end));
        return periods
            .into_iter()
            .rev()
            .take(5)
            .collect::<Vec<_>>()
            .into_iter()
            .rev()
            .map(|period| match field {
                "cash_and_equivalents" => period.cash_and_equivalents,
                "short_term_investments" => period.short_term_investments,
                "accounts_receivable_net" => period.accounts_receivable_net,
                "inventory" => period.inventory,
                "other_current_assets" => period.other_current_assets,
                "total_current_assets" => period.total_current_assets,
                "property_plant_and_equipment_net" => period.property_plant_and_equipment_net,
                "goodwill" => period.goodwill,
                "intangible_assets" => period.intangible_assets,
                "other_non_current_assets" => period.other_non_current_assets,
                "total_assets" => period.total_assets,
                "accounts_payable" => period.accounts_payable,
                "accrued_expenses_and_other_current_liabilities" => {
                    period.accrued_expenses_and_other_current_liabilities
                }
                "short_term_debt" => period.short_term_debt,
                "total_current_liabilities" => period.total_current_liabilities,
                "long_term_debt" => period.long_term_debt,
                "other_non_current_liabilities" => period.other_non_current_liabilities,
                "total_liabilities" => period.total_liabilities,
                "retained_earnings" => period.retained_earnings,
                "accumulated_other_comprehensive_income_loss" => {
                    period.accumulated_other_comprehensive_income_loss
                }
                "total_equity" => period.total_equity,
                "total_shares_outstanding" | "shares_outstanding" => period.shares_outstanding,
                _ => None,
            })
            .collect();
    }

    let Some(financials) = financials else {
        return Vec::new();
    };
    let mut periods = financials.periods.clone();
    periods.sort_by(|left, right| left.period_end.cmp(&right.period_end));
    periods
        .into_iter()
        .rev()
        .take(5)
        .collect::<Vec<_>>()
        .into_iter()
        .rev()
        .map(|period| match field {
            "total_revenues" | "revenue" => period.revenue,
            "operating_income" | "ebit" => period.operating_income,
            "gross_profit" => period.gross_profit,
            "net_income" => period.net_income,
            "diluted_eps" => period.diluted_eps,
            "shares_outstanding"
            | "total_shares_outstanding"
            | "diluted_weighted_average_shares_outstanding" => period.shares_outstanding,
            _ => None,
        })
        .collect()
}

fn market_binding_values(source_key: &str, company: Option<&Company>) -> Vec<Option<f64>> {
    let Some(company) = company else {
        return Vec::new();
    };
    vec![match binding_field(source_key) {
        "current_price" | "price" => Some(company.price),
        "market_cap" => Some(company.market_cap),
        "52_week_high" | "high52_week" => company.high52_week,
        "52_week_low" | "low52_week" => company.low52_week,
        "pe" => company.pe,
        "eps" => company.eps,
        _ => None,
    }]
}

fn write_binding_values(
    workbook_json: &mut Value,
    binding: &ModelDataBinding,
    values: &[Option<f64>],
) {
    let addresses = expand_range_addresses(&binding.range);
    let Some(sheets) = workbook_json
        .get_mut("sheets")
        .and_then(Value::as_array_mut)
    else {
        return;
    };
    let Some(sheet) = sheets
        .iter_mut()
        .find(|sheet| sheet.get("id").and_then(Value::as_str) == Some(binding.sheet_id.as_str()))
    else {
        return;
    };
    let Some(cells) = sheet.get_mut("cells").and_then(Value::as_object_mut) else {
        return;
    };

    for (address, value) in addresses.into_iter().zip(values.iter()) {
        let Some(value) = value else {
            continue;
        };
        let cell = cells.entry(address).or_insert_with(|| json!({}));
        if cell.get("formula").is_some() {
            continue;
        }
        if let Some(object) = cell.as_object_mut() {
            object.insert("value".to_string(), json!(value));
            object.insert(
                "binding".to_string(),
                json!({
                    "kind": binding_kind_key(&binding.kind),
                    "field": binding_field(&binding.source_key),
                }),
            );
        }
    }
}

fn binding_kind_key(kind: &ModelDataBindingKind) -> &'static str {
    match kind {
        ModelDataBindingKind::SecEdgar => "sec_edgar",
        ModelDataBindingKind::Mapping => "mapping",
        ModelDataBindingKind::Market => "market",
    }
}

fn expand_range_addresses(range: &str) -> Vec<String> {
    let (start, end) = range.split_once(':').unwrap_or((range, range));
    let Some((start_col, start_row)) = parse_cell_address(start) else {
        return Vec::new();
    };
    let Some((end_col, end_row)) = parse_cell_address(end) else {
        return Vec::new();
    };
    let mut addresses = Vec::new();
    for row in start_row.min(end_row)..=start_row.max(end_row) {
        for col in start_col.min(end_col)..=start_col.max(end_col) {
            addresses.push(format!("{}{}", column_name(col), row));
        }
    }
    addresses
}

fn parse_cell_address(address: &str) -> Option<(usize, usize)> {
    let mut letters = String::new();
    let mut digits = String::new();
    for char in address.replace('$', "").chars() {
        if char.is_ascii_alphabetic() && digits.is_empty() {
            letters.push(char.to_ascii_uppercase());
        } else if char.is_ascii_digit() {
            digits.push(char);
        } else {
            return None;
        }
    }
    if letters.is_empty() || digits.is_empty() {
        return None;
    }
    let col = letters
        .chars()
        .fold(0usize, |acc, char| {
            acc * 26 + (char as usize - 'A' as usize + 1)
        })
        .checked_sub(1)?;
    let row = digits.parse::<usize>().ok()?;
    Some((col, row))
}

fn column_name(mut col: usize) -> String {
    col += 1;
    let mut name = String::new();
    while col > 0 {
        let remainder = (col - 1) % 26;
        name.insert(0, (b'A' + remainder as u8) as char);
        col = (col - 1) / 26;
    }
    name
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::{inferred_historical_range, sec_binding_values};
    use crate::terminal::{
        BalanceSheetPanelData, FilingRef, Frequency, SourceStatus, StatementPeriod,
    };

    fn statement_period(period_end: &str, cash: f64, assets: f64) -> StatementPeriod {
        StatementPeriod {
            label: period_end.to_string(),
            fiscal_year: Some(period_end[..4].to_string()),
            fiscal_period: Some("FY".to_string()),
            period_start: None,
            period_end: period_end.to_string(),
            filed_date: period_end.to_string(),
            form: "10-K".to_string(),
            revenue: None,
            gross_profit: None,
            operating_income: None,
            net_income: None,
            diluted_eps: None,
            cash_and_equivalents: Some(cash),
            short_term_investments: None,
            accounts_receivable_net: None,
            inventory: None,
            other_current_assets: None,
            total_current_assets: None,
            property_plant_and_equipment_net: None,
            goodwill: None,
            intangible_assets: None,
            other_non_current_assets: None,
            total_assets: Some(assets),
            accounts_payable: None,
            accrued_expenses_and_other_current_liabilities: None,
            short_term_debt: None,
            total_current_liabilities: None,
            long_term_debt: None,
            other_non_current_liabilities: None,
            total_liabilities: None,
            retained_earnings: None,
            accumulated_other_comprehensive_income_loss: None,
            total_equity: None,
            shares_outstanding: None,
            book_value_per_share: None,
        }
    }

    #[test]
    fn three_statement_inferred_bindings_refresh_only_historical_columns() {
        let workbook = json!({
            "metadata": {
                "mosaiciq": {
                    "template": "three_statement",
                    "historicalYears": 3
                }
            }
        });

        assert_eq!(inferred_historical_range(&workbook, 8), "B8:D8");
    }

    #[test]
    fn default_inferred_bindings_keep_legacy_five_column_range() {
        assert_eq!(inferred_historical_range(&json!({}), 4), "B4:F4");
    }

    #[test]
    fn sec_binding_values_reads_balance_sheet_fields() {
        let balance_sheet = BalanceSheetPanelData {
            symbol: "AAPL".to_string(),
            company_name: "Apple Inc.".to_string(),
            cik: "0000320193".to_string(),
            frequency: Frequency::Annual,
            periods: vec![
                statement_period("2024-09-28", 100.0, 1000.0),
                statement_period("2023-09-30", 90.0, 900.0),
                statement_period("2025-09-27", 110.0, 1100.0),
            ],
            latest_filing: None::<FilingRef>,
            source_status: SourceStatus {
                companyfacts_used: true,
                degraded_reason: None,
            },
            available_concepts: Vec::new(),
            selected_currency: Some("USD".to_string()),
            available_currencies: vec!["USD".to_string()],
            mapping_warnings: Vec::new(),
            available_periods: Vec::new(),
            selected_period_start: None,
            selected_period_end: None,
        };

        let values = sec_binding_values("cash_and_equivalents", None, Some(&balance_sheet), None);

        assert_eq!(values, vec![Some(90.0), Some(100.0), Some(110.0)]);
    }
}
