use std::collections::HashMap;
use std::sync::Arc;

use hamelin_lib::err::TemplateParameterSpecError;
use hamelin_lib::tree::options::{
    TemplateParameterKind, TemplateParameterSpecSerde, TemplateParameterType,
    TemplateParameterValue,
};
use serde::Deserialize;
use tsify::Tsify;
use wasm_bindgen::JsValue;

/// JSON / TS shape for [`TemplateParameterKind`] in WASM bindings.
#[derive(Debug, Deserialize, Tsify)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[tsify(from_wasm_abi)]
pub enum WasmTemplateParameterKind {
    Primitive {
        #[serde(rename = "type")]
        typ: TemplateParameterType,
    },
    IdentifierFragment {
        values: Vec<String>,
    },
}

/// JSON / TS shape for [`TemplateParameterValue`] when substituting template parameters.
#[derive(Debug, Deserialize, Tsify)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[tsify(from_wasm_abi)]
pub enum WasmTemplateParameterValue {
    Boolean { value: bool },
    Int { value: i64 },
    Double { value: f64 },
    String { value: String },
    Interval { value: String },
}

/// Forward into [`TemplateParameterSpecSerde`] and reuse its [`TryFrom`] validation, so
/// every entry point (CLI JSON, WASM JS) hits the exact same rules for empty / duplicate
/// `identifier_fragment` domains.
impl From<WasmTemplateParameterKind> for TemplateParameterSpecSerde {
    fn from(kind: WasmTemplateParameterKind) -> Self {
        match kind {
            WasmTemplateParameterKind::Primitive { typ } => {
                TemplateParameterSpecSerde::Primitive { typ }
            }
            WasmTemplateParameterKind::IdentifierFragment { values } => {
                TemplateParameterSpecSerde::IdentifierFragment { values }
            }
        }
    }
}

/// Parse optional `template_parameters` object from JS (`undefined` → `None`).
pub fn parse_template_parameters_js(
    raw: Option<JsValue>,
) -> Result<Option<Arc<HashMap<String, TemplateParameterKind>>>, JsValue> {
    let Some(raw) = raw else {
        return Ok(None);
    };
    if raw.is_null() || raw.is_undefined() {
        return Ok(None);
    }
    let map: HashMap<String, WasmTemplateParameterKind> =
        serde_wasm_bindgen::from_value(raw).map_err(|e| JsValue::from_str(&e.to_string()))?;
    let mut out = HashMap::with_capacity(map.len());
    for (k, v) in map {
        let spec: TemplateParameterSpecSerde = v.into();
        let kind: TemplateParameterKind = spec
            .try_into()
            .map_err(|e: TemplateParameterSpecError| JsValue::from_str(&e.to_string()))?;
        out.insert(k, kind);
    }
    Ok(Some(Arc::new(out)))
}

/// Parse optional `template_values` object from JS (`undefined` / missing → empty map).
pub fn parse_template_values_js(
    raw: Option<JsValue>,
) -> Result<HashMap<String, TemplateParameterValue>, JsValue> {
    let Some(raw) = raw else {
        return Ok(HashMap::new());
    };
    if raw.is_null() || raw.is_undefined() {
        return Ok(HashMap::new());
    }
    let map: HashMap<String, WasmTemplateParameterValue> =
        serde_wasm_bindgen::from_value(raw).map_err(|e| JsValue::from_str(&e.to_string()))?;
    let mut out = HashMap::with_capacity(map.len());
    for (k, v) in map {
        let value = match v {
            WasmTemplateParameterValue::Boolean { value } => TemplateParameterValue::Boolean(value),
            WasmTemplateParameterValue::Int { value } => TemplateParameterValue::Int(value),
            WasmTemplateParameterValue::Double { value } => TemplateParameterValue::Double(value),
            WasmTemplateParameterValue::String { value } => TemplateParameterValue::String(value),
            WasmTemplateParameterValue::Interval { value } => {
                TemplateParameterValue::Interval(value)
            }
        };
        out.insert(k, value);
    }
    Ok(out)
}
