use std::sync::Arc;

use hamelin_lib::catalog::{Catalog, CatalogProvider, Column};
use hamelin_lib::err::{ContextualTranslationErrors, WithTranslationErrors};
use hamelin_lib::provider::EnvironmentProvider;
use hamelin_lib::tree::ast::query::Query;
use hamelin_lib::tree::ast::ParseWithErrors;
use hamelin_lib::type_check_with_provider;
use serde::Serialize;
use tsify::Tsify;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;

#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct CompileHamelinQueryResultOk {
    pub columns: Vec<Column>,
}

#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub enum CompileHamelinQueryResult {
    Ok(CompileHamelinQueryResultOk),
    Err(ContextualTranslationErrors),
}

#[wasm_bindgen]
pub fn compile_query(
    query: String,
    catalog: Catalog,
) -> Result<CompileHamelinQueryResult, JsValue> {
    let catalog_provider = Arc::new(
        CatalogProvider::try_from(catalog)
            .map_err(|e| JsValue::from_str(e.to_string().as_str()))?,
    );
    Ok(compile_query_with_provider(query, catalog_provider))
}

pub fn compile_query_with_provider(
    query: String,
    provider: Arc<dyn EnvironmentProvider>,
) -> CompileHamelinQueryResult {
    let (ast, parse_errors) = Query::parse_with_errors(query.clone());
    let WithTranslationErrors {
        output: typed_statement,
        errors: semantic_errors,
    } = type_check_with_provider::<Query>(Arc::new(ast), provider);
    let mut errors = parse_errors;
    errors.extend(semantic_errors);
    if !errors.is_empty() {
        return CompileHamelinQueryResult::Err(ContextualTranslationErrors::new(query, errors));
    }

    CompileHamelinQueryResult::Ok(CompileHamelinQueryResultOk {
        columns: typed_statement.schema().into(),
    })
}

#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub enum QueryDatasetsResult {
    Ok(Vec<String>),
    Err(ContextualTranslationErrors),
}

#[wasm_bindgen]
pub fn get_statement_datasets(query: String) -> QueryDatasetsResult {
    let (ast, errors) = Query::parse_with_errors(query.clone());
    if !errors.is_empty() {
        return QueryDatasetsResult::Err(ContextualTranslationErrors::new(query, errors));
    }

    match ast.datasets() {
        Ok(datasets) => {
            QueryDatasetsResult::Ok(datasets.into_iter().map(|id| id.to_string()).collect())
        }
        Err(error) => QueryDatasetsResult::Err(ContextualTranslationErrors::new(
            query,
            (*error).clone().single(),
        )),
    }
}
