//! Hamelin core library — parser, AST, type-checker, and translation logic.
//!
//! Start with [`parse`] to get an AST, then [`type_check`] to type-check it.
//! Each function has a `_with_options` variant that takes an options struct
//! built via `bon::Builder` for customising behaviour (interner, provider,
//! time range, etc.).
//!
//! All functions return [`WithTranslationErrors<T>`] which can be converted to
//! `Result` via `.result()`, or destructured to access `.output` and `.errors`.

extern crate core;

use std::sync::Arc;

use crate::err::WithTranslationErrors;
use crate::provider::{EnvironmentProvider, NoOpProvider};
use crate::tree::ast::context::ParseContext;
use crate::tree::ast::expression::Expression;
use crate::tree::ast::query::Query;
use crate::tree::ast::{ParseWithContext, TypeCheck};
use crate::tree::options::{ParseOptions, TypeCheckOptions};
use crate::tree::typed_ast::context::ExpressionTranslationContext;
use crate::tree::typed_ast::context::StatementTranslationContext;
use crate::tree::typed_ast::expression::TypedExpression;
use crate::tree::typed_ast::query::TypedStatement;

pub use crate::time_range::TimeRange;
pub use crate::tree::options::{ExpressionTypeCheckOptions, TranslateOptions};

pub mod antlr;
mod cast_semantics;
pub mod catalog;
pub mod completion;
pub mod err;
pub mod func;
pub mod interner;
pub mod operator;
pub mod parser;
pub mod provider;
pub mod sql;
pub mod time_range;
pub mod translation;
pub mod tree;
pub mod types;
mod write_utils;

pub use cast_semantics::{f64_to_i64, parse_string_as_bool, parse_timestamp_to_utc};
pub use write_utils::Indent;

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

// ---------------------------------------------------------------------------
// Parse
// ---------------------------------------------------------------------------

/// Parse a Hamelin query string into an AST.
///
/// Uses default options. Call `.result()` to get `Result`, or access
/// `.output` / `.errors` directly.
pub fn parse(input: impl Into<String>) -> WithTranslationErrors<Query> {
    parse_with_options(input, ParseOptions::default())
}

/// Parse a Hamelin query string with configurable options.
pub fn parse_with_options(
    input: impl Into<String>,
    opts: ParseOptions,
) -> WithTranslationErrors<Query> {
    let ctx = match opts.interner {
        Some(interner) => ParseContext::with_interner(interner),
        None => ParseContext::new(),
    };
    let (output, errors) = Query::parse_with_context(input.into(), ctx);
    WithTranslationErrors::new(output, errors)
}

// ---------------------------------------------------------------------------
// Type-check
// ---------------------------------------------------------------------------

/// Type-check a parsed AST with default options.
///
/// Works for both `Query` and `Pipeline` (anything implementing `TypeCheck`).
pub fn type_check<Ast: TypeCheck>(ast: impl Into<Arc<Ast>>) -> WithTranslationErrors<Ast::Output> {
    type_check_with_options(ast, TypeCheckOptions::default())
}

pub fn type_check_with_provider<Ast: TypeCheck>(
    ast: impl Into<Arc<Ast>>,
    provider: Arc<dyn EnvironmentProvider>,
) -> WithTranslationErrors<Ast::Output> {
    type_check_with_options(ast, TypeCheckOptions::builder().provider(provider).build())
}

pub fn type_check_with_time_range<Ast: TypeCheck>(
    ast: impl Into<Arc<Ast>>,
    provider: Arc<dyn EnvironmentProvider>,
    time_range: Option<Arc<Expression>>,
) -> WithTranslationErrors<Ast::Output> {
    type_check_with_options(
        ast,
        TypeCheckOptions::builder()
            .provider(provider)
            .maybe_time_range(time_range)
            .build(),
    )
}

/// Type-check a parsed AST with configurable options.
///
/// Works for both `Query` and `Pipeline` (anything implementing `TypeCheck`).
pub fn type_check_with_options<Ast: TypeCheck>(
    ast: impl Into<Arc<Ast>>,
    opts: TypeCheckOptions,
) -> WithTranslationErrors<Ast::Output> {
    let mut ctx = StatementTranslationContext::new(opts.registry, opts.provider);
    if let Some(interner) = opts.interner {
        ctx = ctx.with_interner(interner);
    }
    if let Some(time_range) = opts.time_range {
        ctx = ctx.with_time_range(time_range);
    }
    if let Some(timestamp_field) = opts.timestamp_field {
        ctx = ctx.with_timestamp_field(timestamp_field);
    }
    if let Some(message_field) = opts.message_field {
        ctx = ctx.with_message_field(message_field);
    }
    let output = Ast::type_check_with_context(ast.into(), &mut ctx);
    let errors = ctx.take_errors();
    WithTranslationErrors::new(output, errors)
}

// ---------------------------------------------------------------------------
// Combined parse + type-check
// ---------------------------------------------------------------------------

/// Parse and type-check a Hamelin query string in one step.
pub fn parse_and_typecheck(input: impl Into<String>) -> WithTranslationErrors<TypedStatement> {
    parse_and_typecheck_with_options(input, TypeCheckOptions::default())
}

/// Parse and type-check a Hamelin query string with configurable options.
///
/// The `interner` field in `TypeCheckOptions` is shared across both parse and
/// type-check passes.
pub fn parse_and_typecheck_with_options(
    input: impl Into<String>,
    opts: TypeCheckOptions,
) -> WithTranslationErrors<TypedStatement> {
    let parse_opts = ParseOptions {
        interner: opts.interner.clone(),
    };
    let WithTranslationErrors {
        output: ast,
        errors: parse_errors,
    } = parse_with_options(input, parse_opts);

    let WithTranslationErrors {
        output: typed,
        errors: tc_errors,
    } = type_check_with_options(ast, opts);

    let mut all_errors = parse_errors;
    all_errors.extend(tc_errors);
    WithTranslationErrors::new(typed, all_errors)
}

// ---------------------------------------------------------------------------
// Expression type-check (used internally, not a top-level API)
// ---------------------------------------------------------------------------

/// Type-check an expression AST node with configurable options.
pub fn type_check_expression(
    ast: impl Into<Arc<Expression>>,
    opts: ExpressionTypeCheckOptions,
) -> WithTranslationErrors<TypedExpression> {
    let ast = ast.into();
    let provider = Arc::new(NoOpProvider::default());
    let mut pipeline_ctx = StatementTranslationContext::new(opts.registry.clone(), provider);
    if let Some(interner) = opts.interner {
        pipeline_ctx = pipeline_ctx.with_interner(interner);
    }
    let mut expr_ctx = ExpressionTranslationContext::new(
        opts.bindings,
        opts.registry,
        opts.fctx,
        &mut pipeline_ctx,
    );

    let typed_expr = TypedExpression::from_ast_with_context(ast, &mut expr_ctx);
    let errors = pipeline_ctx.take_errors();
    WithTranslationErrors::new(typed_expr, errors)
}
