//! High-level workflow APIs for WASM.
//!
//! Provides single-molecule reports, multi-molecule comparisons, and batch screening
//! as JSON strings suitable for JavaScript/TypeScript integration.

use wasm_bindgen::prelude::*;

/// Generate a complete molecular report (JSON string) from a SMILES.
/// Returns the JSON representation of a `MoleculeReport` struct.
///
/// # Example (JS)
/// ```javascript
/// const json = module.molecule_report_json("CC(=O)Oc1ccccc1C(=O)O");
/// const report = JSON.parse(json);
/// console.log(report.canonical_smiles, report.descriptors.tpsa);
/// ```
#[wasm_bindgen]
pub fn molecule_report_json(smiles: &str) -> Result<String, JsValue> {
    let report = chematic_chem::molecule_report(smiles)
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    serde_json::to_string(&report)
        .map_err(|e| JsValue::from_str(&format!("JSON serialization failed: {}", e)))
}

/// Compare two or more SMILES strings (JSON string output).
/// Returns the JSON representation of a `MoleculeComparison` struct.
///
/// # Example (JS)
/// ```javascript
/// const json = module.compare_molecules_json("c1ccccc1", "Cc1ccccc1");
/// const comparison = JSON.parse(json);
/// console.log(comparison.pairwise[0].similarities.ecfp4_tanimoto);
/// ```
#[wasm_bindgen]
pub fn compare_molecules_json(smiles1: &str, smiles2: &str) -> Result<String, JsValue> {
    let smiles = [smiles1, smiles2];
    let comparison = chematic_chem::compare_molecules(&smiles)
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    serde_json::to_string(&comparison)
        .map_err(|e| JsValue::from_str(&format!("JSON serialization failed: {}", e)))
}

/// Compare multiple SMILES strings (up to 256 by default).
/// Accepts a delimiter-separated list (e.g., newline or comma).
///
/// # Example (JS)
/// ```javascript
/// const smilesList = "c1ccccc1\nCc1ccccc1\nCCc1ccccc1";
/// const json = module.compare_molecules_batch_json(smilesList, "\n");
/// const comparison = JSON.parse(json);
/// ```
#[wasm_bindgen]
pub fn compare_molecules_batch_json(
    smiles_batch: &str,
    delimiter: &str,
) -> Result<String, JsValue> {
    let smiles_vec: Vec<&str> = smiles_batch.split(delimiter).collect();
    let comparison = chematic_chem::compare_molecules(&smiles_vec)
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    serde_json::to_string(&comparison)
        .map_err(|e| JsValue::from_str(&format!("JSON serialization failed: {}", e)))
}

/// Screen a batch of SMILES strings (JSON string output).
/// Returns per-record results including pass/fail with error details.
/// Includes MaxMin diversity picking and Butina clustering by default.
///
/// # Example (JS)
/// ```javascript
/// const smilesList = "c1ccccc1\nCC\nCCC";
/// const json = module.screen_smiles_json(smilesList, "\n");
/// const report = JSON.parse(json);
/// console.log(report.records); // Array of ScreeningRecord
/// console.log(report.maxmin_picks); // Diversity-selected indices
/// console.log(report.butina_clusters); // Clustering result
/// ```
#[wasm_bindgen]
pub fn screen_smiles_json(smiles_batch: &str, delimiter: &str) -> String {
    let smiles_vec: Vec<&str> = smiles_batch.split(delimiter).collect();
    let report = chematic_chem::screen_smiles(&smiles_vec);
    serde_json::to_string(&report)
        .unwrap_or_else(|_| "{\"error\": \"JSON serialization failed\"}".to_string())
}

/// Generate 3D coordinates from SMILES (raw distance geometry, no minimization).
/// Returns PDB format string with atoms positioned in 3D space.
///
/// # Example (JS)
/// ```javascript
/// const pdbStr = module.generate_3d_from_smiles("c1ccccc1");
/// console.log(pdbStr);  // PDB file content
/// ```
#[wasm_bindgen]
pub fn generate_3d_from_smiles(smiles: &str) -> Result<String, JsValue> {
    let mol = chematic_smiles::parse(smiles)
        .map_err(|e| JsValue::from_str(&format!("SMILES parse error: {}", e)))?;
    let coords = chematic_3d::generate_coords(&mol);
    let pdb_str = chematic_3d::write_pdb(&mol, &coords);
    Ok(pdb_str)
}

/// Generate 3D coordinates and minimize from SMILES string.
/// Pipeline: distance geometry → DREIDING minimization.
/// Better geometry quality than raw DG; suitable for graphics.
///
/// # Example (JS)
/// ```javascript
/// const pdbStr = module.generate_3d_optimized_pdb("c1ccccc1");
/// console.log(pdbStr);  // PDB file with optimized geometry
/// ```
#[wasm_bindgen]
pub fn generate_3d_optimized_pdb(smiles: &str) -> Result<String, JsValue> {
    let mol = chematic_smiles::parse(smiles)
        .map_err(|e| JsValue::from_str(&format!("SMILES parse error: {}", e)))?;
    let coords = chematic_3d::generate_and_minimize_dreiding(&mol);
    let pdb_str = chematic_3d::write_pdb(&mol, &coords);
    Ok(pdb_str)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn workflow_json_serialization_aspirin() {
        let report = chematic_chem::molecule_report("CC(=O)Oc1ccccc1C(=O)O").unwrap();
        let json = serde_json::to_string(&report).unwrap();
        assert!(json.contains("molecular_weight"), "JSON should contain molecular_weight");
        assert!(json.contains("tpsa"), "JSON should contain tpsa");
    }

    #[test]
    fn workflow_json_serialization_compare() {
        let comparison = chematic_chem::compare_molecules(&["c1ccccc1", "Cc1ccccc1"]).unwrap();
        let json = serde_json::to_string(&comparison).unwrap();
        assert!(json.contains("pairwise"), "JSON should contain pairwise");
        assert!(json.contains("ecfp4_tanimoto"), "JSON should have similarity metrics");
    }

    #[test]
    fn workflow_json_serialization_screen() {
        let report = chematic_chem::screen_smiles(&["c1ccccc1", "CC", "CCC"]);
        let json = serde_json::to_string(&report).unwrap();
        assert!(json.contains("records"), "JSON should contain records");
        assert!(json.contains("maxmin_picks"), "JSON should contain diversity picks");
    }

    #[test]
    fn distance_geometry_benzene_has_coords() {
        let mol = chematic_smiles::parse("c1ccccc1").unwrap();
        let coords = chematic_3d::generate_coords(&mol);
        assert_eq!(coords.atom_count(), 6, "benzene has 6 carbons");
    }

    #[test]
    fn distance_geometry_ethane_reasonable_distance() {
        let mol = chematic_smiles::parse("CC").unwrap();
        let coords = chematic_3d::generate_coords(&mol);
        let p0 = coords.get(chematic_core::AtomIdx(0));
        let p1 = coords.get(chematic_core::AtomIdx(1));
        let dist = p0.distance(&p1);
        assert!((dist - 1.54).abs() < 0.15, "C-C distance should be ~1.54 Å");
    }
}
