use std::rc::Rc;
use std::sync::Arc;

use crate::err::TranslationErrors;
use crate::func::def::FunctionTranslationContext;
use crate::func::registry::FunctionRegistry;
use crate::interner::Interner;
use crate::provider::{EnvironmentProvider, NoOpProvider};
use crate::tree::typed_ast::context::{ExpressionTranslationContext, StatementTranslationContext};
use crate::tree::typed_ast::environment::TypeEnvironment;
use crate::tree::typed_ast::expression::TypedExpression;

use self::context::ParseContext;

pub mod clause;
pub mod command;
pub mod context;
pub mod expression;
pub mod identifier;
pub mod node;
pub mod ops;
pub mod pattern;
pub mod pipeline;
pub mod query;

/// Trait for types that perform actual type-checking work (Options types).
///
/// This trait is implemented by Options types like `QueryTranslationOptions` and
/// `ExpressionTranslationOptions`, which do the actual work of type-checking an AST node.
///
/// ## Implementation Requirements
///
/// Implementors only need to provide `typed_with_errors()`, which performs the type-checking
/// and returns both the typed AST and any errors. The other two methods have default
/// implementations that delegate to it.
pub trait TypeCheckExecutor: Sized {
    /// The typed AST output type
    type Output;

    /// Type-check and return the typed AST, ignoring errors (they're embedded in the output).
    ///
    /// Default implementation just calls `typed_with_errors()` and discards the errors.
    fn typed(self) -> Self::Output {
        self.typed_with_errors().0
    }

    /// Type-check and return `Result`, failing if any errors occur.
    ///
    /// Default implementation calls `typed_with_errors()` and checks if errors are empty.
    fn typed_result(self) -> Result<Self::Output, TranslationErrors> {
        let (output, errors) = self.typed_with_errors();
        if errors.is_empty() {
            Ok(output)
        } else {
            Err(errors)
        }
    }

    /// Type-check and return both the typed AST and all errors.
    ///
    /// This is the only method implementors need to provide.
    fn typed_with_errors(self) -> (Self::Output, TranslationErrors);
}

/// Trait for type-checking untyped AST nodes (Query, Pipeline, Expression) into typed AST.
///
/// This trait is implemented by AST node types that delegate to Options types for
/// the actual type-checking work.
///
/// ## Implementation Requirements
///
/// Implementors only need to provide `typed_with()`, which returns an Options builder.
/// All other methods have default implementations that delegate to the Options type.
pub trait IntoTyped {
    /// The typed AST output type (e.g., TypedStatement, TypedQueryPipeline, TypedExpression)
    type Output;

    /// The translation options builder type for customizing type-checking.
    /// Must implement `TypeCheckExecutor` to perform the actual type-checking.
    type Options: TypeCheckExecutor<Output = Self::Output>;

    /// Type-check with default settings, embedding errors as `Error` nodes in the tree.
    ///
    /// **Use this if:** Building language servers/IDEs that need partial results, or analyzing
    /// code with errors.
    ///
    /// Always succeeds. Errors become `Error` nodes in the typed AST.
    fn typed(self) -> Self::Output
    where
        Self: Sized,
    {
        self.typed_with().typed()
    }

    /// Type-check and return `Result`, failing if any errors occur.
    ///
    /// **Use this if:** In a compiler/CLI where errors should stop execution, or you only
    /// want the typed AST when fully valid.
    ///
    /// Returns `Err` containing all errors if type-checking failed.
    fn typed_result(self) -> Result<Self::Output, TranslationErrors>
    where
        Self: Sized,
    {
        self.typed_with().typed_result()
    }

    /// Type-check and return both the typed AST and a collection of all errors.
    ///
    /// **Use this if:** You need to report errors to users but continue processing,
    /// or want batch error reporting with the typed AST.
    ///
    /// Always returns the typed AST (with embedded error nodes) plus all errors found.
    fn typed_with_errors(self) -> (Self::Output, TranslationErrors)
    where
        Self: Sized,
    {
        self.typed_with().typed_with_errors()
    }

    /// Get an options builder to customize type-checking (registry, provider, etc).
    ///
    /// **Use this if:** You need custom function registries, specific schema providers,
    /// or mock dependencies for testing.
    ///
    /// Returns an Options builder that implements `TypeCheckExecutor`.
    ///
    /// ## Implementation Note
    ///
    /// This is the only method implementors need to provide. Return an Options builder
    /// (e.g., `QueryTranslationOptions::from_ast(Rc::new(self))`).
    fn typed_with(self) -> Self::Options;
}

/// Trait for parsing strings into untyped AST nodes (Query, Pipeline, Expression, Identifier).
///
/// Provides three parsing methods with different error handling strategies.
/// See individual methods for when to use each.
///
/// ## Implementation Requirements
///
/// Implementors only need to provide `parse_with_errors()`. The other two methods have
/// default implementations that delegate to it.
pub trait ParseWithErrors: Sized {
    /// Parse with default settings, embedding errors as `Error` nodes in the tree.
    ///
    /// **Use this if:** Building language servers/IDEs that need partial results, or analyzing
    /// code with syntax errors. The parser will recover and produce a best-effort AST.
    ///
    /// Always succeeds. Parse errors become `Error` nodes in the untyped AST.
    fn parse(input: impl Into<String>) -> Self {
        Self::parse_with_errors(input).0
    }

    /// Parse and return `Result`, failing if any errors occur.
    ///
    /// **Use this if:** In a compiler/CLI where parse errors should stop execution, or you only
    /// want the AST when fully valid.
    ///
    /// Returns `Err` containing all errors if parsing failed (even one error causes failure).
    fn parse_result(input: impl Into<String>) -> Result<Self, TranslationErrors> {
        let (ast, errors) = Self::parse_with_errors(input);
        if errors.is_empty() {
            Ok(ast)
        } else {
            Err(errors)
        }
    }

    /// Parse and return both the AST and a collection of all errors.
    ///
    /// **Use this if:** You need to report parse errors to users but continue processing,
    /// or want batch error reporting with the AST.
    ///
    /// Always returns the AST (with embedded error nodes) plus all parse errors found.
    ///
    /// ## Implementation Note
    ///
    /// This is the only method implementors need to provide. The other methods delegate to this.
    fn parse_with_errors(input: impl Into<String>) -> (Self, TranslationErrors);
}

/// Trait for types that perform actual parsing work (Options types).
///
/// This trait is implemented by Options types like `ParseOptions`,
/// which do the actual work of parsing input.
pub trait ParseExecutor: Sized {
    /// The AST output type
    type Output;

    /// Parse and return the AST, ignoring errors (they're embedded in the output).
    fn parse(self) -> Self::Output {
        self.parse_with_errors().0
    }

    /// Parse and return `Result`, failing if any errors occur.
    fn parse_result(self) -> Result<Self::Output, TranslationErrors> {
        let (ast, errors) = self.parse_with_errors();
        if errors.is_empty() {
            Ok(ast)
        } else {
            Err(errors)
        }
    }

    /// Parse and return both the AST and all errors.
    ///
    /// This is the only method implementors need to provide.
    fn parse_with_errors(self) -> (Self::Output, TranslationErrors);
}

/// Trait for parsing strings into AST nodes with configurable options.
///
/// This is the counterpart to `IntoTyped` for the parsing phase.
/// Provides a builder pattern for configuring parse options like string interning.
pub trait IntoParsed: ParseWithErrors {
    /// The parse options builder type for customizing parsing.
    type Options: ParseExecutor<Output = Self>;

    /// Get an options builder to customize parsing (interner, etc).
    ///
    /// **Use this if:** You need string interning for memory efficiency,
    /// or other custom parse-time configurations.
    ///
    /// Returns a ParseOptions builder that implements `ParseExecutor`.
    fn parse_with(input: impl Into<String>) -> Self::Options;
}

/// Parse options for configuring how AST nodes are parsed.
///
/// Use the fluent API to customize options, then call `.parse()`, `.parse_result()`, or
/// `.parse_with_errors()` to perform parsing.
pub struct ParseOptions<Ast: ParseWithContext> {
    input: String,
    interner: Option<Arc<Interner>>,
    _marker: std::marker::PhantomData<Ast>,
}

impl<Ast: ParseWithContext> ParseOptions<Ast> {
    /// Create new options with default settings
    pub fn new(input: impl Into<String>) -> Self {
        Self {
            input: input.into(),
            interner: None,
            _marker: std::marker::PhantomData,
        }
    }

    /// Set a string interner for memory-efficient identifier storage.
    ///
    /// When set, all identifier strings go through the interner,
    /// deduplicating identical strings to share the same allocation.
    pub fn interner(mut self, interner: Arc<Interner>) -> Self {
        self.interner = Some(interner);
        self
    }
}

impl<Ast: ParseWithContext> ParseExecutor for ParseOptions<Ast> {
    type Output = Ast;

    fn parse_with_errors(self) -> (Ast, TranslationErrors) {
        let ctx = match self.interner {
            Some(interner) => ParseContext::with_interner(interner),
            None => ParseContext::new(),
        };
        Ast::parse_with_context(self.input, ctx)
    }
}

/// Trait for AST types that can be parsed with a custom context.
///
/// This is the internal trait that types implement to support configurable parsing.
/// Users interact with `IntoParsed` and `ParseOptions` instead.
pub trait ParseWithContext: Sized {
    /// Parse input with the given context.
    fn parse_with_context(input: String, ctx: ParseContext) -> (Self, TranslationErrors);
}

/// Trait for AST types that can be type-checked with a translation context.
///
/// This trait abstracts the type-checking process for Query and Pipeline nodes,
/// allowing generic translation options to work for both types.
pub trait TypeCheck {
    /// The typed AST output type
    type Output;

    /// Type-check this AST node with the given translation context.
    /// The context provides the function registry, environment provider, and error accumulation.
    fn type_check_with_context(
        ast: Rc<Self>,
        ctx: &mut StatementTranslationContext,
    ) -> Self::Output;
}

/// Translation options for type-checking Query or Pipeline AST nodes.
///
/// Configures the function registry and environment provider used during type-checking.
/// Use the fluent API to customize options, then call `.typed()`, `.typed_result()`, or
/// `.typed_with_errors()` to perform type-checking.
pub struct QueryTranslationOptions<Ast: TypeCheck> {
    ast: Rc<Ast>,
    registry: Arc<FunctionRegistry>,
    provider: Arc<dyn EnvironmentProvider>,
}

impl<Ast: TypeCheck> QueryTranslationOptions<Ast> {
    /// Create new options with default registry and provider
    pub fn from_ast(ast: Rc<Ast>) -> Self {
        Self {
            ast,
            registry: Arc::new(FunctionRegistry::default()),
            provider: Arc::new(NoOpProvider::default()),
        }
    }

    /// Set a custom function registry
    pub fn with_registry(mut self, registry: Arc<FunctionRegistry>) -> Self {
        self.registry = registry;
        self
    }

    /// Set a custom environment provider (for table schemas)
    pub fn with_provider(mut self, provider: Arc<dyn EnvironmentProvider>) -> Self {
        self.provider = provider;
        self
    }
}

impl<Ast: TypeCheck> TypeCheckExecutor for QueryTranslationOptions<Ast> {
    type Output = Ast::Output;

    fn typed_with_errors(self) -> (Self::Output, TranslationErrors) {
        let mut ctx =
            StatementTranslationContext::new(self.registry.clone(), self.provider.clone());
        let output = Ast::type_check_with_context(self.ast.clone(), &mut ctx);
        let errors = ctx.take_errors();
        (output, errors)
    }
}

/// Translation options for type-checking Expression AST nodes.
///
/// Unlike Query/Pipeline options which take an `EnvironmentProvider`, Expression options
/// take `bindings` (the already-resolved environment at the expression's location) and
/// `fctx` (function translation context for window functions, etc).
///
/// Use the fluent API to customize options, then call `.typed()`, `.typed_result()`, or
/// `.typed_with_errors()` to perform type-checking.
pub struct ExpressionTranslationOptions {
    ast: Rc<expression::Expression>,
    registry: Arc<FunctionRegistry>,
    bindings: Rc<TypeEnvironment>,
    fctx: FunctionTranslationContext,
}

impl ExpressionTranslationOptions {
    /// Create new options with defaults from an Rc-wrapped AST
    pub fn from_ast(ast: Rc<expression::Expression>) -> Self {
        Self {
            ast,
            registry: Arc::new(FunctionRegistry::default()),
            bindings: Rc::new(TypeEnvironment::default()),
            fctx: FunctionTranslationContext::default(),
        }
    }

    /// Set a custom function registry
    pub fn with_registry(mut self, registry: Arc<FunctionRegistry>) -> Self {
        self.registry = registry;
        self
    }

    /// Set custom environment bindings (available columns/variables)
    pub fn with_bindings(mut self, bindings: Rc<TypeEnvironment>) -> Self {
        self.bindings = bindings;
        self
    }

    /// Set custom function translation context (for window functions, etc.)
    pub fn with_fctx(mut self, fctx: FunctionTranslationContext) -> Self {
        self.fctx = fctx;
        self
    }
}

impl TypeCheckExecutor for ExpressionTranslationOptions {
    type Output = TypedExpression;

    fn typed_with_errors(self) -> (TypedExpression, TranslationErrors) {
        let provider = Arc::new(NoOpProvider::default());
        let mut pipeline_ctx = StatementTranslationContext::new(self.registry.clone(), provider);
        let mut expr_ctx = ExpressionTranslationContext::new(
            self.bindings,
            self.registry,
            self.fctx,
            &mut pipeline_ctx,
        );

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

        (typed_expr, errors)
    }
}
