#![allow(dead_code)]
use std::sync::Arc;

use hamelin_lib::ast::err::ContextualTranslationErrors;
use hamelin_lib::catalog::{Catalog, CatalogProvider as LibCatalogProvider, DataSetBuilder};
use hamelin_lib::translation::{ContextualResult, QueryTranslation};
use hamelin_lib::FunctionDescription;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tsify_next::Tsify;
use wasm_bindgen::prelude::*;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

#[wasm_bindgen]
struct CatalogProvider {
    wrapped: Arc<LibCatalogProvider>,
}

#[wasm_bindgen]
impl CatalogProvider {
    pub fn try_from_catalog(catalog: Catalog) -> Result<Self, JsValue> {
        Ok(Self {
            wrapped: Arc::new(
                LibCatalogProvider::try_from(catalog)
                    .map_err(|e| JsValue::from_str(e.to_string().as_str()))?,
            ),
        })
    }
}

#[wasm_bindgen]
struct Compiler {
    wrapped: hamelin_lib::Compiler,
}

#[wasm_bindgen]
impl Compiler {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        Self {
            wrapped: hamelin_lib::Compiler::new(),
        }
    }

    pub fn get_function_descriptions(&self) -> Vec<FunctionDescription> {
        self.wrapped.get_function_descriptions()
    }

    pub fn set_catalog_provider(&mut self, provider: CatalogProvider) {
        self.wrapped.set_environment_provider(provider.wrapped);
    }

    #[cfg(target_arch = "wasm32")]
    pub fn set_time_range(&mut self, start: Option<js_sys::Date>, end: Option<js_sys::Date>) {
        use chrono::{DateTime, Utc};
        use hamelin_lib::TimeRange;

        self.wrapped.set_time_range(
            TimeRange {
                start: start.map(|d| DateTime::<Utc>::from(d)),
                end: end.map(|d| DateTime::<Utc>::from(d)),
            }
            .into(),
        );
    }

    pub fn set_time_range_expression(
        &mut self,
        expression: String,
    ) -> Option<ContextualTranslationErrors> {
        match self.wrapped.set_time_range_expression(expression) {
            Ok(_) => None,
            Err(e) => Some(e),
        }
    }

    pub fn compile_query(&self, query: String) -> CompileQueryResult {
        let res = self.wrapped.compile_query(query);
        match res {
            Ok(t) => CompileQueryResult::Ok(t),
            Err(e) => CompileQueryResult::Err(e),
        }
    }

    pub fn compile_query_at(&self, query: String, at: Option<usize>) -> ContextualResult {
        self.wrapped.compile_query_at(query, at)
    }

    pub fn get_statement_datasets(&self, query: String) -> QueryDatasetsResult {
        match self.wrapped.get_statement_datasets(query) {
            Ok(datasets) => QueryDatasetsResult::Ok(datasets),
            Err(e) => QueryDatasetsResult::Err(e),
        }
    }
}

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

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

#[derive(Serialize, Deserialize, Tsify)]
#[serde(rename_all = "camelCase")]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct CatalogResource {
    pub name: String,
    pub query: String,
}

#[derive(Debug, Error, Serialize, Tsify)]
#[tsify(into_wasm_abi)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum BuildCatalogError {
    #[error("failed to initialize catalog: {message}")]
    CatalogInit { message: String },

    #[error("query compilation failed")]
    Compilation {
        name: String,
        errors: ContextualTranslationErrors,
    },

    #[error("failed to parse dataset: {message}")]
    DatasetParse { name: String, message: String },
}

#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi, hashmap_as_object)]
pub struct BuildCatalogOutput {
    pub catalog: Catalog,
    pub errors: Vec<BuildCatalogError>,
}

#[wasm_bindgen]
/// Builds a `Catalog` by compiling each dataset query and registering its output schema.
///
/// Important: `resources` (datasets) must be topologically sorted before being passed in:
/// if dataset B references dataset A, then A must appear before B in `resources`.
pub fn build_catalog(
    starting_catalog: Catalog,
    resources: Vec<CatalogResource>,
) -> BuildCatalogOutput {
    // Initialize compiler and catalog provider from starting catalog
    let catalog_provider = match LibCatalogProvider::try_from(starting_catalog) {
        Ok(provider) => provider,
        Err(e) => {
            return BuildCatalogOutput {
                catalog: Catalog::default(),
                errors: vec![BuildCatalogError::CatalogInit {
                    message: e.to_string(),
                }],
            };
        }
    };

    let provider_arc: Arc<LibCatalogProvider> = Arc::new(catalog_provider);
    let mut compiler = hamelin_lib::Compiler::new();
    compiler.set_environment_provider(provider_arc.clone());

    let mut errors = Vec::new();

    // Process each resource in order
    for resource in resources {
        match compiler.compile_query(resource.query.clone()) {
            Ok(query_translation) => {
                // Build a DataSetBuilder from the translation columns and use its parse method
                let mut builder = DataSetBuilder::new(&resource.name);
                for column in query_translation.translation.columns {
                    builder.columns.push(column);
                }

                // Use DataSetBuilder::parse() which handles all the identifier parsing and type conversion
                match builder.parse() {
                    Ok((name_identifier, column_map)) => {
                        provider_arc.set(name_identifier, column_map);
                    }
                    Err(e) => {
                        errors.push(BuildCatalogError::DatasetParse {
                            name: resource.name,
                            message: e.to_string(),
                        });
                    }
                }
            }
            Err(e) => {
                // Compilation failed - add to errors but continue
                errors.push(BuildCatalogError::Compilation {
                    name: resource.name,
                    errors: e,
                });
            }
        }
    }

    // Get the final catalog from the provider
    let final_catalog = provider_arc.get_catalog();

    BuildCatalogOutput {
        catalog: final_catalog,
        errors,
    }
}
