use hamelin_lib::err::ContextualTranslationErrors;
use hamelin_lib::tree::ast::query::Query;
use hamelin_lib::tree::ast::ParseWithErrors;
use hamelin_lib::tree::{substitute_query, validate_template_values};
use serde::Serialize;
use tsify::Tsify;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;

use crate::wasm_template_params::{parse_template_parameters_js, parse_template_values_js};

#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct SubstituteTemplateParametersOk {
    pub query: String,
}

#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub enum SubstituteTemplateParametersResult {
    Ok(SubstituteTemplateParametersOk),
    Err(ContextualTranslationErrors),
    /// Substitution failed for a reason that does not anchor to a query span (for example a
    /// missing or wrong-typed `template_values` entry, or `identifier_fragment` domain rules). Surfaced
    /// without fabricating a `0..=0` source span so the IDE doesn't render a fake underline.
    SubstitutionError(String),
}

/// Bad `template_values` JSON is reported as a `JsValue` exception (matching
/// [`crate::compile::compile_query`]); query parse errors flow through
/// [`SubstituteTemplateParametersResult::Err`] and substitution failures
/// (no-span issues) flow through [`SubstituteTemplateParametersResult::SubstitutionError`].
///
/// When `template_parameters` is provided, supplied values are validated against the declared spec
/// before substitution (kind matching, `IdentifierFragment` domain enforcement). Without it, substitution
/// runs unvalidated — the caller is responsible for ensuring values are well-typed.
#[wasm_bindgen]
pub fn substitute_template_parameters(
    query: String,
    template_values: Option<JsValue>,
    template_parameters: Option<JsValue>,
) -> Result<SubstituteTemplateParametersResult, JsValue> {
    let values = parse_template_values_js(template_values)?;
    let specs = parse_template_parameters_js(template_parameters)?;

    if let Some(specs) = &specs {
        if let Err(e) = validate_template_values(specs, &values) {
            return Ok(SubstituteTemplateParametersResult::SubstitutionError(
                e.to_string(),
            ));
        }
    }

    let (ast, parse_errors) = Query::parse_with_errors(query.clone());
    if !parse_errors.is_empty() {
        return Ok(SubstituteTemplateParametersResult::Err(
            ContextualTranslationErrors::new(query, parse_errors),
        ));
    }

    Ok(match substitute_query(&ast, values) {
        Ok(out) => SubstituteTemplateParametersResult::Ok(SubstituteTemplateParametersOk {
            query: out.to_string(),
        }),
        Err(e) => SubstituteTemplateParametersResult::SubstitutionError(e.to_string()),
    })
}
